在上一篇隨筆: ActiveX(二)Js 監聽 ActiveX中的事件 中,已經可以實現 Js 監聽 ActiveX中的事件,至此、Js 和 ActiveX 已經可以實現雙向通訊了。但是、這樣的實現方式,都是站在Js的角度去實現的,那么 ActiveX 能否主動調用 Js 呢?答案無疑是肯定的,在該篇隨筆中、我們將逐漸揭開這一層神秘的面紗。
我第一次接觸用C#代碼調用Js是在四年前,那時候正在實習做Windows應用、需要借用 WebBrowser 控件操作js、完成一些特殊需求。當時的代碼大致如下:
// 執行JS private void ExecJs() { try { if (this.webBrowser.Document != null) { mshtml.IHTMLDocument2 currentDoc = (mshtml.IHTMLDocument2)this.webBrowser.Document.DomDocument; if (currentDoc != null) { mshtml.IHTMLWindow2 win = (mshtml.IHTMLWindow2)currentDoc.parentWindow; if (win != null) { //調用 函數F、傳遞一個寬度做參數 win.execScript(string.Format("F('{0}')", this.webBrowser.Width), "javascript"); } } } } catch (Exception ex) { Console.WriteLine(ex.Message); } }
由上述代碼、可以看出,其核心方法為 mshtml.IHTMLWindow2.execScript,注意:我們再仔細看一下方法名,有沒有覺得很熟悉?如果你還沒想起來,那我們繼續看一下 mshtml.IHTMLWindow2 這個類型中還有什么成員吧。

[Guid("332C4427-26CB-11D0-B483-00C04FD90119")] [TypeLibType(4160)] public interface IHTMLWindow2 : IHTMLFramesCollection2 { [DispId(1153)] dynamic _newEnum { get; } [DispId(1161)] HTMLNavigator clientInformation { get; } [DispId(23)] bool closed { get; } [DispId(1101)] string defaultStatus { get; set; } [DispId(1151)] IHTMLDocument2 document { get; } [DispId(1152)] IHTMLEventObj @event { get; } [DispId(1169)] dynamic external { get; } [DispId(1100)] FramesCollection frames { get; } [DispId(2)] HTMLHistory history { get; } [DispId(1125)] HTMLImageElementFactory Image { get; } [DispId(1001)] int length { get; } [DispId(14)] HTMLLocation location { get; } [DispId(11)] string name { get; set; } [DispId(5)] HTMLNavigator navigator { get; } [DispId(1164)] dynamic offscreenBuffering { get; set; } [DispId(-2147412073)] dynamic onbeforeunload { get; set; } [DispId(-2147412097)] dynamic onblur { get; set; } [DispId(-2147412083)] dynamic onerror { get; set; } [DispId(-2147412098)] dynamic onfocus { get; set; } [DispId(-2147412099)] dynamic onhelp { get; set; } [DispId(-2147412080)] dynamic onload { get; set; } [DispId(-2147412076)] dynamic onresize { get; set; } [DispId(-2147412081)] dynamic onscroll { get; set; } [DispId(-2147412079)] dynamic onunload { get; set; } [DispId(4)] dynamic opener { get; set; } [DispId(1157)] HTMLOptionElementFactory Option { get; } [DispId(12)] IHTMLWindow2 parent { get; } [DispId(1156)] IHTMLScreen screen { get; } [DispId(20)] IHTMLWindow2 self { get; } [DispId(1102)] string status { get; set; } [DispId(21)] IHTMLWindow2 top { get; } [DispId(22)] IHTMLWindow2 window { get; } [DispId(1105)] void alert(string message = ""); [DispId(1159)] void blur(); [DispId(1163)] void clearInterval(int timerID); [DispId(1104)] void clearTimeout(int timerID); [DispId(3)] void close(); [DispId(1110)] bool confirm(string message = ""); [DispId(1165)] dynamic execScript(string code, string language = "JScript"); [DispId(1158)] void focus(); [DispId(0)] dynamic item(ref object pvarIndex); [DispId(7)] void moveBy(int x, int y); [DispId(6)] void moveTo(int x, int y); [DispId(25)] void navigate(string url); [DispId(13)] IHTMLWindow2 open(string url = "", string name = "", string features = "", bool replace = false); [DispId(1111)] dynamic prompt(string message = "", string defstr = "undefined"); [DispId(8)] void resizeBy(int x, int y); [DispId(9)] void resizeTo(int x, int y); [DispId(1160)] void scroll(int x, int y); [DispId(1167)] void scrollBy(int x, int y); [DispId(1168)] void scrollTo(int x, int y); [DispId(1173)] int setInterval(string expression, int msec, ref object language = Type.Missing); [DispId(1172)] int setTimeout(string expression, int msec, ref object language = Type.Missing); [DispId(1155)] void showHelp(string helpURL, object helpArg = Type.Missing, string features = ""); [DispId(1154)] dynamic showModalDialog(string dialog, ref object varArgIn = Type.Missing, ref object varOptions = Type.Missing); [DispId(1166)] string toString(); }
想起來了沒有? 如果還沒有、那只能說你一行Js代碼都沒寫過。 沒錯,js 中的 window 對象 實現了 mshtml.IHTMLWindow2 這個接口。既然如此,我們通過一個初始化函數將 window 對象傳入,就可以得到 mshtml.IHTMLWindow2 接口了。
哈哈,補充一下, mshtml 命名空間 源自:Microsoft.mshtml.dll.
當知道了如上的信息后,一切就變的‘簡單’了:
/// <summary> /// 調用Js函數 /// </summary> /// <param name="functionName">js函數名</param> /// <param name="args">參數</param> /// <returns></returns> private dynamic RunJs(string functionName, string args = "") { dynamic dy = null; // 方法返回值永遠為 null if (this.window2 != null) { //調用函數 dy = this.window2.execScript(string.Format("{0}('{1}')", functionName, args), "javascript"); } return dy; }
通過如上的代碼,確實可以調用Js、但是、還有一個Bug... 返回值一直為Null.
那怎么辦呢? 反射——沒錯,就是反射調用Js,也許你現在還想不明白, 先看一下代碼吧,看完后你也許會恍然大悟:
/// <summary> /// 調用Js函數 /// </summary> /// <param name="functionName">js函數名</param> /// <param name="args">參數</param> /// <returns></returns> private dynamic RunJs(string functionName, string args = "") { dynamic dy = null;// 第二種方式 反射調用 Js // 被調用的js函數必須有一個參數 if (this.window2 != null) { dy = this.window2.GetType().InvokeMember(functionName, BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, this.window2, new object[] { args }); } return dy; }
有沒有想到呢 —— 全局的Js函數其實是屬於window對象的,當然可以用反射去調用啦。
完整的測試項目demo: TestActiveX.zip
下一篇,我們將探究強大的 Microsoft.mshtml.dll,有點期待。
(未完待續...)