C#でお手軽ハイブリッドアプリケーション (2)
前書き
前回で、最新IE(事実上、Vista以外ならIE11)を使ったハイブリッドアプリケーションの基本形ができました。
今回は、C#側とJavaScript側で、データのやり取りをしたいと思います。しかも(やや不便ながら)配列やオブジェクトも含めて。
JavaScriptとC#でやり取りする
基本的なやり取りは、とても簡単です。
C#からJavaScriptへの値の引き渡し
webBrowser.Document.InvokeScript(string, object[]); を呼び出すだけ。
function test1() { alert("test1 called"); }
こういう関数を呼び出すなら、C#側は、
webBrowser.Document.InvokeScript("test1");
ですし、
function test2(arg1, arg2) { alert("test2 called : arg1=" + arg1 + " / arg2=" + arg2); }
のようにJavaScript側関数に引数があれば、C#側も第2引数に入れればOKです。
webBrowser.Document.InvokeScript("test2", new string[] { "str1", "str2" });
JavaScriptからC#への値の引き渡し
こちらは、C#側で呼び出し対象のクラスを決めておき、それをWebBrowser.ObjectForScriptingに登録すればOKです。
ここでは、仮に呼び出し対象クラスを独立に作ってみます。別にFormとかでも構わないようですが。
[ComVisible(true)] public class Callee { public void CalleeTest1() { MessageBox.Show("CalleeTest1 called."); } public void CalleeTest2(string arg1, string arg2) { MessageBox.Show("CalleeTest2 called. arg1=" + arg1 + " /// arg2=" + arg2); } }
引数なしと引数あり、2種類の関数だけを入れてみました。クラスのComVisible属性は必須です。
これをWebBrowser側に登録します。
private void Form1_Load(object sender, EventArgs e) { // 略 webBrowser.ObjectForScripting = new Callee(); }
簡易的ですが、これでJavaScript側からの呼び出しが可能になります。
window.external.CalleeTest1(); window.external.CalleeTest2("string1","string2");
こんな感じで呼び出します。引数の数は整合しないとうまくいきません。
配列やオブジェクトのやり取り
上記でC#とJavaScriptのやり取りが可能ですが、あくまで、引数や戻り値としては、数値や文字列など、単純なものしか使えません。
という風に各種の方法が提案されています。
でも、個人的には、もっと簡単でもいいんじゃないかなーとか思ったりします。
JavaScriptでオブジェクトを表現といえば、JSON。JSONにすれば文字列でやり取りできるので、もう何もこわくありません。
JavaScript側で配列だの連想配列だのオブジェクトだのをJSONにするならwindow.JSON.stringify
ですね。
// Object -> JSON var obj = { i: 42, f: 3.14, b: true, s: "[Hello, World!]" }; window.external.CalleeTest3(window.JSON.stringify(obj));
そして、JSONをオブジェクトなどにするにはwindow.JSON.parse
ですね。
var obj = window.JSON.parse(arg);
一方、C#でJSONというと、Json.NETやDynamicJsonなどもありますが、標準でも.NET3.5からはDataContractJsonSerializer
があります。今回は標準でいってみます。
なお、C#でJSONやXMLを使う場合の注意点については、DynamicJson作者によるまとめが便利です。
neue cc - .NETの標準シリアライザ(XML/JSON)の使い分けまとめ
上記によるDataContractJsonSerializer
の弱点は次の通りです。
まあ、C#がWebBrowserをホストする場合、JSONはあくまでC#とJavaScriptのやり取り用なので、これらはあまり問題とはならないでしょう。
まずは、参照設定に.NET項目を追加します。追加するのはSystem.Runtime.Serialization
とSystem.ServiceModel.Web
。後者が曲者で、DataContractJsonSerializer
に必要なんですよね。なんか名前全然違いますけど。
その上で、ソース側に名前空間を追加しておきましょうか。
using System.Runtime.Serialization; using System.Runtime.Serialization.Json;
ここでusing行に波線が出る場合は、参照設定ができていないものと思われます。
ここまで問題がなければ、次はJavaScriptとやり取りするデータ用の型を作ります。
[DataContract] public class Data { [DataMember] public int i { get; set; } [DataMember] public float f { get; set; } [DataMember] public bool b { get; set; } [DataMember] public string s { get; set; } }
とりあえず感に満ちたクラスですが、大事なのはDataContract
とDataMember
属性。これをつけないと処理できません。
ここまで来れば、いよいよJSON処理です。……といっても、案外面倒なんですよね。そこで任意型に対応するメソッドを書いてみました。
public static string GetJsonString<T>(T src) where T : class { if (src == null) return null; var jsoner = new DataContractJsonSerializer(typeof(T)); var mem = new MemoryStream(); jsoner.WriteObject(mem, src); string json = null; try { json = Encoding.UTF8.GetString(mem.ToArray()); } catch (DecoderFallbackException) { } return json; }
public static T GetObjectFromJson<T>(string json) where T : class { var jsonee = new DataContractJsonSerializer(typeof(T)); byte[] bytes = null; try { bytes = Encoding.UTF8.GetBytes(json); } catch (EncoderFallbackException) { } if (bytes == null) return null; var mem = new MemoryStream(bytes); var obj = (T)jsonee.ReadObject(mem); return obj; }
Data d = new Data { i = 1234, f = 2.8F, b = false, s = "From C#" }; string json = null; try { json = JsonUtility.GetJsonString<Data>(d); } catch (Exception) { } if (json == null) { MessageBox.Show("JSONize failed."); return; } webBrowser1.Document.InvokeScript("test3", new string[] { json });
Data obj = null; try { obj = JsonUtility.GetObjectFromJson<Data>(json); } catch (Exception) { } if (obj == null) { MessageBox.Show("CalleeTest3 : failed"); } else { MessageBox.Show( string.Format("CalleeTest3 : int={0} float={1} bool={2} str={3}", obj.i, obj.f, obj.b, obj.s)); }
例外処理がちょっとかっこ悪いですね。
でも、これでC#とJavaScript間のやり取りは一通りできるようになりました。めでたしめでたし。