為什么要讓VC與JavaScript交互?
1.有時候我們需要讓自己的軟件打開一個網頁,來獲取頁面上的一些數據。這時,可以用mshtml解析HTML提取出數據,也可以向HTML文檔動態寫入我們准備好的JS代碼,用JS代碼獲取HTML上的數據,然后用VC調用該JS代碼取得數據。
2.有時候我們需要讓自己的軟件打開一個網頁並操控該網頁,填寫表單,提交等動作。這時,可以用mshtml操作HTML,給文本框賦值,模擬點擊按鈕。也可以向HTML文檔動態寫入我們准備好的JS代碼,用JS代碼實現填單,提交等動作,然后用VC調用一下JS代碼即可。
3.有時候我們需要用HTML網頁做界面,用JS管理HTML頁面,用VC調用JS傳遞進數據,JS把這些數據通過HTML顯示在界面上。CHtmlDialog正是這種產物。
4.有時候我們需要基於HTTP協議與WEB服務器直接交互。比如基於HTTP協議來登錄QQ空間。但是對瀏覽器抓包發現QQ號是明文傳輸,但QQ密碼是密文,它是如何加密的呢?這些加密算法肯定都在頁面上的JS里,因為這個加密過程是在客戶瀏覽器上實現的。我們可以找到加密相關的JS代碼,仔細閱讀分析,改寫為C++代碼實現,但比較復雜。一個簡單的辦法就是直接把這些用於加密的JS代碼復制出來,用VC調用JS代碼,讓它的JS代碼完成加密過程,然后我們把密文拿過來,用HTTP協議POST發送到WEB服務器,即完成了登陸動作。
5.等等,還有很多用途。
那么,在Windows平台上用VC開發的程序,如何與JavaScript交互?
通常,我們都是用WebBrowser加載包含JS代碼的HTML,然后通過WebBrowser對象獲取IHTMLDocument接口(對於ActiveX的WebBrowser來說是get_Document方法,對於CHtmlView對象來說是GetHtmlDocument方法)。其中IHTMLDocument2接口有一個get_Script方法,可以獲取用於控制JS代碼的IDispatch接口。VC調用JS函數,都是通過這個IDispatch接口的Invoke方法來完成。使用IDispatch接口的GetIDsOfNames方法根據JS函數名獲取調度標識符DISPID,使用Invoke來調用JS函數。Invoke最后兩個參數用於返回錯誤信息,可為NULL。這個IDispatch接口調用起來很麻煩,下文將會介紹如何簡便的調用。網上有個外國人寫了個CWebPage類實現VC與JS交互,用的正是這種方法。
http://www.codeproject.com/Articles/2352/JavaScript-call-from-C
關於WebBrowser:
在VC中使用WebBrowser,一般有兩種方法。MFC中有個CHtmlView封裝了WebBrowser,用起來很方便。雖然CHtmlView派生於CView,是個視圖類,但它也派生於CWnd,將其用於對話框上,完全沒有問題,只是在某些地方需要小修改一下。其中需要注意的兩個問題就是:
1. CHtmlView的構造函數是protected的,不允許直接構造一個CHtmlView對象。必須從CHtmlView派生后再構造。
2.如果是在棧上創建CHtmlView對象,必須重載PostNcDestroy並什么也不寫。因為默認的PostNcDestroy會delete this;而出錯。如果是在堆上創建CHtmlView對象則要注意防止二次delete。
另一種方法是使用WebBrowser的ActiveX控件,這種方法可以在MFC項目中使用,也可以在非MFC項目中使用。
關於IDispatch:
我們知道IDispatch是COM雙接口中的調度接口。一般用於供腳本語言調用COM組件。對於編譯型的C++語言,讓它調用這種接口,是很麻煩的。畢竟用IDispatch接口調用COM對象的各種方法、設置與獲取COM對象的屬性、讓COM對象回調我們,都是用IDispatch的Invoke方法來實現。一個Invoke就要實現那么多功能,用起來當然很麻煩。不過好在ATL智能指針類中的CComDispatchDriver(即CComQIPtr<IDispatch>)封裝了IDispatch接口,使用我們用起來大大的方便!CComDispatchDriver對GetIDsOfNames和Invoke進一步進行了封裝,只需更少的參數即方便可調用。
獲取和設置COM對象屬性可以用CComDispatchDriver的這些方法:
GetProperty
GetPropertyByName
PutProperty
PutPropertyByName
其實使用IDispatch調度接口來設置、獲取COM屬性,調用COM方法,都是使用GetIDsOfNames和Invoke。實際上這四個方法都是對GetIDsOfNames和Invoke的封裝。簡化調用的復雜性。
調用COM對象的方法可以用這些方法:
Invoke0 //調用0個參數的方法
Invoke1 //調用1個參數的方法
Invoke2 //調用2個參數的方法
InvokeN //調用多個參數的方法
這些函數都有兩個版本,一個是接受調度標示符DISPID,需要自己先調用GetIDsOfNames來獲取。一個是接受OLE字符串的版本,這個版本在內部會調用GetIDsOfNames來獲取DISPID。這些函數用起來很方便,不需要我們自己填充DISPPARAMS結構,但是它對原始Invoke的調用時,最后兩個參數都是NULL,即不需要獲取錯誤信息。如果需要獲取錯誤信息,我們需要自行調用原始Invoke方法。
注意,這些方法是ALT的CComDispatchDriver封裝的方法,調用時應使用"."而不是"->"。因為"->"獲得的是CComDispatchDriver內部的Dispatch指針。
另一個要注意的問題是,一定要等Navigate完全加載一個html文檔后(觸發OnDocumentComplete)。才能獲取IHTMLDocument2和Script。否則會出現空指針或找不到JS函數。所以不能在調用Navigate打開HTML后就緊接着獲取IHTMLDocument2和Script,要等HTML文檔加載完。
上面說了這么多COM對象,和VC調用JS有什么關系?別忘了我們用IHTMLDocument2接口的get_Script方法獲取到了代表HTML文檔中JS代碼的IDispatch接口,我們用IDispatch接口,把HTML文檔中的這堆JS代碼當作一個COM對象,來操控它。上面說的Invoke0,Invoke1,Invoke2,InvokeN,正是分別被我們用來調用0個參數的JS函數,1個參數的JS函數,2個參數的JS函數,N個參數的JS函數。
說了那么多,下一篇文章,讓我們來實際動手,用VC調用一下JS函數看看。
