質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.60%

C#のコンソールアプリケーションでWebBrowserとのやり取りができない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,140

score 35

前提・実現したいこと

C#で外部から読み込んだJava Scriptで処理をしようとしたのですが、
Windowsアプリケーションでは動くものが、コンソールアプリケーションでは動きません。

Windowsアプリケーションと同じ動作をさせるためには、何が必要ですか。
どこに問題がありますか。

追記
私が最終的にしたいことはWeb BrowserでJava Scriptを処理させ、C#で結果を受け取ることです。フォームを表示せずにWeb Browserを通じてJava Scriptとやりとりをしたいのです。

発生している問題・エラーメッセージ

WindowsアプリケーションではHello, world!と表示されるが、
コンソールアプリケーションではnullになる。

追記
Zuishinさんの指摘により、ソースコードが比較できるように変更したところ、
Windowsアプリケーションでも動作が怪しくなったため、
Windowsアプリケーションでも、コンソールアプリケーションでも動作がうまくいかない問題に直面しています。
(詳しくは追記を見てください。)

私のソースコードはどのような動作をしているのでしょうか。

該当のソースコード

追記の方を見てください。

Windows アプリケーションの場合

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsAppTest
{
    public partial class Form1 : Form
    {
        public ParseResult parseResult;


        public Form1()
        {
            InitializeComponent();

            parseResult = new ParseResult();
            MainForm_Load();
        }

        public void MainForm_Load()
        {
            webBrowser1.DocumentText = @"
<html>
<head>
    <meta charset=""utf-8"" />
    <meta http-equiv=""X-UA-Compatible"" content=""IE=11"" />
    <title>タイトル</title>
</head>
<body>
    <script>
        function cs_func(text, mode) {
            window.external.Result = ""Hello, world!"";
        }
    </script>
    <h1>Hello!</h1>
</body>
</html>
";
            webBrowser1.ObjectForScripting = parseResult;
        }

        [ComVisible(true)]
        public class ParseResult
        {
            public string Result { get; set; }
            public void SetResult(string s) { Result = s; }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            webBrowser1.Document.InvokeScript("cs_func", new string[] { "test", "test 2" });
            Console.WriteLine(parseResult.Result);
        }
    }
}

Button1を押すと、button1_clickが呼び出され、コンソールにHello, world!と表示されます。

コンソールアプリケーションの場合

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace LojbanParser
{

    public class LojbanParser
    {
        public string Text { get; set; }
        public object Result { get; private set; }
        public WebBrowser WebBrowser { get; } = new WebBrowser();
        public LojbanParser() { }
        public LojbanParser(string text) { Text = text; }
        public string Parse()
        {
            var parse = new ParseResult();
            WebBrowser.DocumentText = @"
<html>
<head>
    <meta charset=""utf-8"" />
    <meta http-equiv=""X-UA-Compatible"" content=""IE=11"" />
    <title>title</title>
</head>
<body>
    <script>
        function cs_func(text, mode) {
            external.Result = ""Hello, world!"";
        }
    </script>
    <h1>Hello!</h1>
</body>
</html>
";
            WebBrowser.ObjectForScripting = parse;
            WebBrowser.Document.InvokeScript("cs_func", new string[] { "test", "test 2" });
            return parse.Result;
        }

        [ComVisible(true)]
        public class ParseResult
        {
            public string Result { get; set; }
        }
    }

    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            var parser = new LojbanParser();
            var result = parser.Parse();
            Console.WriteLine(result ?? "(null)");
        }
    }
}

(null)と表示されます。

追記

Zuishinさんの指摘により、ソースコードを同じにしてみようとしてみました。

まずコンソールアプリケーションの場合です。
コンソールアプリケーションのときは参照から「System.Windows.Forms」を追加しなければなりません。

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ConsoleApp1
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            var parser = new MyClass();
            var result = parser.Parse();
            Console.WriteLine(result ?? "(null)");
        }
    }

    public class MyClass
    {
        public string Text { get; set; }
        public object Result { get; private set; }
        public WebBrowser WebBrowser { get; } = new WebBrowser();
        public ParseResult ParseResultObject { get; set; } = new ParseResult();
        public string Parse()
        {
            WebBrowser.DocumentText = @"
<html>
<head>
    <meta charset=""utf-8"" />
    <meta http-equiv=""X-UA-Compatible"" content=""IE=11"" />
    <title>title</title>
</head>
<body>
    <script>
        function cs_func(text) {
            external.Result = text;
        }
    </script>
    <h1>Hello!</h1>
</body>
</html>
";
            WebBrowser.ObjectForScripting = ParseResultObject;
            WebBrowser.Document.InvokeScript("cs_func", new string[] { "Hello, world!" });
            return ParseResultObject.Result;
        }

        [ComVisible(true)]
        public class ParseResult
        {
            public string Result { get; set; }
        }
    }
}

次にWindows アプリケーションです。
Form1に同じようなコードを書いてみたところ、
Windows アプリケーションでも(null)が表示されるようになってしまいました。

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public MyClass Parser;

        public Form1()
        {
            InitializeComponent();
            Parser = new MyClass();
            var result = Parser.Parse();
            Console.WriteLine(result ?? "(null)");
        }
    }

    public class MyClass
    {
        public string Text { get; set; }
        public object Result { get; private set; }
        public WebBrowser WebBrowser { get; set; } = new WebBrowser();
        public ParseResult ParseResultObject { get; set; } = new ParseResult();
        public string Parse()
        {
            WebBrowser.DocumentText = @"
<html>
<head>
    <meta charset=""utf-8"" />
    <meta http-equiv=""X-UA-Compatible"" content=""IE=11"" />
    <title>title</title>
</head>
<body>
    <script>
        function cs_func(text) {
            external.Result = text;
        }
    </script>
    <h1>Hello!</h1>
</body>
</html>
";
            WebBrowser.ObjectForScripting = ParseResultObject;
            WebBrowser.Document.InvokeScript("cs_func", new string[] { "Hello, world!" });
            return ParseResultObject.Result;
        }

        [ComVisible(true)]
        public class ParseResult
        {
            public string Result { get; set; }
        }
    }
}

しかし、Form1の上にボタンを設置し、クリックしたときに処理するようコードを変更したところ、
1回だけ押した時は(null)と表示され、2回目以降はHello, world!と表示されるといった不安定な感じで表示されるようになりました。

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public MyClass Parser;

        public Form1()
        {
            InitializeComponent();
            Parser = new MyClass();
        }

        // ここに追加しました。
        private void Button_Click(object sender, EventArgs e)
        {
            var result = Parser.Parse();
            Console.WriteLine(result ?? "(null)");
        }
    }

    public class MyClass
    {
        public string Text { get; set; }
        public object Result { get; private set; }
        public WebBrowser WebBrowser { get; set; } = new WebBrowser();
        public ParseResult ParseResultObject { get; set; } = new ParseResult();
        public string Parse()
        {
            WebBrowser.DocumentText = @"
<html>
<head>
    <meta charset=""utf-8"" />
    <meta http-equiv=""X-UA-Compatible"" content=""IE=11"" />
    <title>title</title>
</head>
<body>
    <script>
        function cs_func(text) {
            external.Result = text;
        }
    </script>
    <h1>Hello!</h1>
</body>
</html>
";
            WebBrowser.ObjectForScripting = ParseResultObject;
            WebBrowser.Document.InvokeScript("cs_func", new string[] { "Hello, world!" });
            return ParseResultObject.Result;
        }

        [ComVisible(true)]
        public class ParseResult
        {
            public string Result { get; set; }
        }
    }
}

全くもって原因が分かりません。
おそらく、Java Scriptの方には問題がない(上記に示すように不安定だが動くときは動くので)とは思うのですが、
原因と対処法を教えていただきたいです。

補足情報

ターゲットフレームワーク: .NET Framework 4.7.1
IDE: Visual Studio 2017
OS: Windows 10

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

その前に両者のコードが同じものじゃないんですが。
window あたりが怪しくありませんか?

追記

結論から言えば、メッセージループが必要だったので、コンソールアプリでも Form を作る必要がありました。
以下がコンソールアプリでのサンプルコードです。
WebBrowser.ObjectForScripting Property を参考にしました。

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Forms;

namespace ConsoleApp1
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            var result = ParserForm.Parse("Hello World");
            Console.WriteLine(result ?? "(null)");
            Console.ReadKey();
        }
    }

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    [ComVisibleAttribute(true)]
    public class ParserForm : Form
    {
        public static string Parse(string message)
        {
            var region = new Region(new GraphicsPath());
            var form = new ParserForm
            {
                Visible = false,
                message = message,
                FormBorderStyle = FormBorderStyle.None,
                Region = region
            };
            form.ShowDialog();
            return form.Result?.ToString();
        }

        private WebBrowser webBrowser1 = new WebBrowser();
        private string message;
        public object Result { get; set; }

        public ParserForm()
        {
            webBrowser1.Dock = DockStyle.Fill;
            Controls.Add(webBrowser1);
            Load += (s, e) =>
            {
                webBrowser1.ObjectForScripting = this;
                webBrowser1.DocumentText = @"
<html>
<head>
    <meta charset=""utf-8"" />
    <meta http-equiv=""X-UA-Compatible"" content=""IE=11"" />
    <title>title</title>
</head>
<body>
    <script>
        function cs_func(text) {
            window.external.Result = text;
        }
    </script>
    <h1>Hello!</h1>
</body>
</html>
";
                Application.DoEvents();
                webBrowser1.Document.InvokeScript("cs_func", new[] { message });
                DialogResult = DialogResult.OK;
            };
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/17 13:08

    すみません、確かに
    Windows アプリケーションで、
    `window.external.Result = ""Hello, world!"";`
    としていたのに対して、
    コンソールアプリケーションで、
    `external.Result = ""Hello, world!"";`
    としていたのは同じではありませんでした。

    しかし、これらのコードを入れ替えても、
    Window アプリケーションは動きましたが、
    コンソールアプリケーションは動きませんでした。

    つまり、`window`がついているかはあまり関係ないと思います。

    キャンセル

  • 2019/03/17 13:12

    そこだけじゃありません。
    まずどちらからでも同じ手順で呼び出せるコードを作り、同じものを使うよう試してください。

    クラス化し、変数などはそのクラスのプロパティにして呼び出しと結果の取得だけをそれぞれのプロジェクトで行うようにしてください。

    全てはこれができてからだと思います。だから私もざっと眺めただけで自分で検証していません。検証できる環境が整っていないからです。

    キャンセル

  • 2019/03/17 14:39 編集

    ソースコードを同じように変更してみました。
    ただ、Windows アプリケーションでもうまくいかなくなってしまいました。
    (追記のところに書きました)

    キャンセル

  • 2019/03/17 18:02

    ああ、ありがとうございます!
    ちゃんと動くようになりました

    キャンセル

+1

たんに表示すべきFormがない、というだけかと。


単純にFormを追加したからといって、Formが表示できるようにはなりません。
Windowsのフォームというのはその背後にWindowsメッセージというなかなか膨大な(?)仕掛けが動いており、そいつも含めて実装する必要があります
まあ、手っ取り早いのは、Formアプリにするってことですが、コンソールアプリにメッセージループを実装するというのも、そんなに難しくないので挑戦してみてもいいかと思います。

「C# Form コンソールアプリ」なんかでぐぐると、両対応なアプリの作成法なんかもでてくるようなので読んでみてはどうでしょう

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/17 15:13

    私がしたいことは**Web BrowserでJava Scriptを処理させ、C#で結果を受け取ること**です。

    例えば、Java Scriptに足し算を依頼したいとします。
    C#側はWeb Browserを通じてJava Scriptに1と2の数値を渡します。Java Scriptは計算して、3を返します。
    C#側は3を受け取り、それをConsole.WriteLineで表示します。

    つまり、フォームを表示したいのではありません。むしろ、フォームを表示せずにWeb Browserを通じてJava Scriptとやりとりをしたいのです。

    フォームを表示せずにやりとりをしたいため、コンソールアプリケーションを選択しました。しかし、上記にも書いた通り、うまくいきません。

    キャンセル

  • 2019/03/17 15:21

    なら、コンソールアプリベースでどーこーしようというより、フォームを非表示にするとかヘッドレスブラウザを使うとか言う方向のほうがいいような気がしますがどうでしょう。

    #コンソールアプリベースでブラウザを開発するんだ!ってのは不可能ではないですが。。

    キャンセル

  • 2019/03/17 16:01

    返信ありがとうございます。
    ヘッドレスブラウザは知りませんでした。
    おそらく、それを使えばやりたいことはできるかもしれません。
    ちょっとやってみます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.60%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る