C#からはHtmlDocument.InvokeScript
でグローバル関数を呼び出せる。evalも呼べる。
でもってWebBrowser.ObjectForScripting
にComVisible(true)
なオブジェクトを設定しておくと、publicなメンバーがJSからアクセス可能になる。
How to: Implement Two-Way Communication Between DHTML Code and Client Application Code
C#でもUIをHTML/CSS/JavaScriptで実装したい! - Qiita
C# から JavaScript を利用する場合の嫌なとこ
JavaScript から C# を利用する場合の嫌なとこ
- 戻り値が配列だと値を受け取れない。
InvokeScriptの正体
WebBrowserコントロールは多分IWebBrowser2。てことはIWebBrowser2を使って同じようにJSを呼び出せるはず。
まずIHTMLWindow2::execScript
なるものがあります。けれどこれは eval
相当のものであって、あたかも目的の関数にオブジェクトを直接渡すかのようなInvokeScript
の動きとは異なります。
ところでIHTMLWindow2のメソッド一覧では「execScript
は古いからeval
使いな」って書いてあります。実はexecScriptというのは以前window.execScript
としてJSから呼び出すことができた。というか、IHTMLWindow2
こそがJavascriptのwindow
オブジェクトだったわけです。
そしてJavascriptのwindowオブジェクトは全てのグローバル変数を抱えています。
ということはIHTMLWindow2
のプロパティを参照すれば、グローバル変数や関数にホスト側から・・・どうやらこれがHtmlDocument.InvokeScript
の正体のようです。
JavaScript call from C++ - CodeProject
InvokeScript改の夢を見た
さて、そうとわかれば以下の問題も解決できるはずです。
- 変数の設定や取得ができない。
- クラス内関数を実行できない。
C#でやるためにはWebBrowser
からIHTMLWindow2
を手に入れる必要があります。COM上の構造と同じように、documentから辿って・・・HtmlWindow.DomWindow
ですね。
それで昨日の時点では
これをdynamicなりInvokeMemberなりで呼び出してやれば、と書こうとして2時間ほど経ちました。残念ながらこれらの機能ではIDispatchを素直に(ぶっつけで)呼ぶことができないようで、
- alertやlocationといったIHTMLWindow2に定義されているメンバは当然呼べる
- evalはエラーになるが、先に
IHTMLWindow2::execScript()
すれば呼べるようになる- 自分で定義したグローバル変数・関数はそれでもだめ
とかいう謎挙動。
じゃあInvokeScriptは結局なにやってんのさってReferenceSourceを見たら、IDispatchを叩いてたという。
って書いてたんですが、ドキュメントを読み込んだ後に Application.DoEvents()
すればよかっただけでした。execScript()
は内部的に似たようなことやってそうです。
InvokeScript | dynamic | Type.InvokeMember | |
---|---|---|---|
eval | ○ | ○ | ○ |
location.host 取得 | ○ | ○ | |
location 代入 | ○ | ○ | |
hoge 呼び出し | ○ | ○ | |
hoge.prop 取得・代入 | ○ | ○ | |
hoge.method 呼び出し | × | × | ○ |
hoge(匿名関数)呼び出し | × | × | ○ |
Date 呼び出し | ○ | ○ | |
new Date() | × | × | × |
dynamicで匿名関数を呼ぼうとすると、関数呼び出しの形を取っているにも関わらず、関数オブジェクトが返ってくるという。Type.GetMethod()
でも取得できないが、BindingFlags.InvokeMethod
を指定してType.InvokeMember()
すればOK。
InvokeScript: コンストラクタ
Javascriptの場合newを付けて関数を呼び出すとコンストラクタになるという仕様ですが、これはCOMの世界から見ても特殊なケースのようです。なんせIDispatch2でこのためのInvokeExメソッドが追加されたんですから。
Wherefore IDispatchEx? - Fabulous Adventures In Coding - Site Home - MSDN Blogs
Creating JavaScript arrays and other objects from C++ - CodeProject
匿名関数を呼び出した時のようにBindingFlags.CreateInstance
が使えるかと思ったものの、COM Interopした型には使えないようで・・・。
ObjectForScripting: インデクサ
気を取り直して、逆方向。
JavascriptのArrayは結局ExpandoObjectみたいな拡張可能なオブジェクトで実装されているので、行きも帰りも厄介です。順番にappendしてやれば作ることは可能でしょうが、以下略。
とはいえC#から公開するだけなら、自前でComVisibleなコレクションを作ることはできます。インデクサを書くだけ。引数は複数にできますし、セッターも動きます。
ただしVBAやJScriptと同様、アクセスにはobj(1)
のように丸括弧を使います。
ObjectForScripting: 動的なメソッド・可変長引数
可変長引数とかできないのかなーってparams
をつけただけでは流石に無理でした、はい。
普通にCOMに公開するだけでIDispatchにも対応させてはくれるわけですが、IReflectというインターフェイスを使うことで、IDispatchへの応答を動的に決めることができます。dynamicに使うDynamicMetaObjectを思い出します。
IReflect インターフェイス (System.Reflection)
.net - C# COM object with a dynamic interface - Stack Overflow
上のQ&Aでは動的に追加削除する例が載っていますが、これを活用して可変長引数にも生かせます。
MethodInfoでは引数の情報も提供するのですが、これより多くても少なくてもエラーにはならないようです。また実際に呼び出されるときにはMethodInfo.Invoke
ではなくIReflect.InvokeMember
が使われるようなので、引数を配列にまとめてparamsに渡すこともできますし、引数の数に応じて別のメソッドを呼ぶことだって可能です。
感想
dynamicで自由自在に呼べるかと思ったんだけどなあ・・・。
おかげで朝日が眩しい。