うならぼ

どうも。

WebBrowser上のJSに配列、オブジェクト、関数を渡す

unarist.hatenablog.com

昨日解決できなかった、配列やオブジェクトをJSに渡すという話をスタックオーバーフローにでも投げようと改めて調べていたら、解決しちゃったお話。

配列を作る

昨日の結論としては、「コンストラクタとして呼べないのでどうしようもない」というものだった。

のだけど、よく考えれば、Array()は普通に関数として呼んでも機能する。試しにdynamicで呼んでみると動くではないか。

Creating JavaScript arrays and other objects from C++ - CodeProject

上の記事のような「存在しないプロパティのDISPIDを取得」することができないらしく、COM経由での要素追加はできなかった。これも push() を使うことで達成。というかArrayの使い方としてはそっちの方が正しいよね・・・。

一度追加されてしまえば取得設定はCOMのプロパティ同様に行える。インデクサじゃなくて、数字がプロパティ名になったオブジェクト。これもType.InvokeMemberなら大丈夫。

dynamic dwindow = browser.Document.Window.DomWindow;
object window = browser.Document.Window.DomWindow;

var arr = window.GetType().InvokeMember(
    "Array", BindingFlags.InvokeMethod, null, window,
    new object[]{ 1, 2, 3 }
);

dynamic darr = arr;
darr.push(4);

var val = arr.GetType().InvokeMember("2", BindingFlags.GetProperty, null, arr, new object[0]);
Console.WriteLine(val);

arr.GetType().InvokeMember("0", BindingFlags.SetProperty, null, arr, new object[] {"first"});

こうなると連想配列というか任意のオブジェクトを作りたくなるが、プロパティの追加ができないので・・・本当にできない?

オブジェクトを作る

これまた再掲。

.net - C# COM object with a dynamic interface - Stack Overflow

このQ&Aの中でさらっと触れられている IExpando というインターフェイス

On a side note, IExpando does the same job for IDispatchEx, so a JavaScript client can add new properties which can later be accesses by managed code.

ということはですよ、IExpandoにキャストできるんじゃない?

まずは空のオブジェクトを作ります。ひょっとしてと思ったら、Object()Array()と似たような動きをしてくれました。

var obj = window.GetType().InvokeMember("Object", BindingFlags.InvokeMethod, null, window, new object[0]);
var expando = (IExpando)obj;
expando.AddProperty("prop").SetValue(expando, "value");

dwindow.hoge(obj); // hogeは適当なfunction

よっしゃ。あれ、ということは。

var expando = (IExpando)window;
expando.AddProperty("prop").SetValue(expando, "value");
dwindow.eval("alert(prop)");

グローバル変数が作れる・・・!ということは。

関数を作る

ここまでの集大成ですね。

var fun = window.GetType().InvokeMember(
    "Function", BindingFlags.InvokeMethod, null, window,
    new object[]{ "alert('Hello world!')" }
);

var expando = (IExpando)window;
expando.AddProperty("myfunc").SetValue(expando, fun);
dwindow.eval("myfunc()");

ちなみに匿名関数なのでdynamicから直接呼ぶことはできません。

もうちょっとIExpandoで遊ぶ

IExpando.GetMembers()をしてみると、どれもPropertyとMethodの両方で登場するという。そしてwindowオブジェクトのメンバーを列挙してもArrayやDateは現れない。これはfor..inを使った時の結果と一致します。

じゃあIExpando.GetMember()Dateは見つからないのかというと、ちゃんと出てきます。ちなみにType.GetMember()では見つかりません。

IExpando.GetProperties()Type.GetProperties()の結果はIHTMLWindow2_**みたいなのを覗けばほとんど同じなんですが、それぞれ存在しないものがあります。IExpando経由でしか列挙できないものとしてはlocalStorageとか。これが実装される頃にはもはやIHTMLWindowが拡張されなくなったんですかね・・・。

その他

  • IExpando.AddMethod(): サポートされていないって怒られた
  • コンストラクタ呼び出し: やっぱりCreateInstanceはダメっぽい

おしまい

まとめた何かをQiitaに投げるかも?

c# - What is IExpando and where is it used? - Stack Overflow

Microsoft had high hopes for JScript, it was a primary language supported along-side C#, VB.NET and Managed C++. That didn't work out.