cef js返回c++的代码_CEF3开发者系列之外篇——IE中JS与C++交互
使用IE內(nèi)核開(kāi)發(fā)客戶端產(chǎn)品,系統(tǒng)和前端頁(yè)面之間的交互,通常給開(kāi)發(fā)和維護(hù)帶來(lái)很大的便利性。但操作系統(tǒng)和前端之間的交互卻是比較復(fù)雜的。具體來(lái)說(shuō)就是腳本語(yǔ)言和編譯語(yǔ)言的交互。在IE內(nèi)核中html和css雖然不兼容,但是IE編程接口是完全一樣的,這得益于微軟的COM組件的結(jié)構(gòu)化設(shè)計(jì)和實(shí)現(xiàn)。所以與IE交互,必須得先說(shuō)一下COM,COM全稱(chēng)組件對(duì)象模型(Component Object Model)。
COM的基本思想很簡(jiǎn)單,所有的組件模塊都提供一個(gè)最根本的接口, IUnkown,它有三個(gè)方法,AddRef和Release實(shí)現(xiàn)了引用計(jì)數(shù),QueryInterface實(shí)現(xiàn)了根據(jù)接口id查詢(xún)另外的接口,所有的接口都從IUnkown派生。基于IE內(nèi)核做開(kāi)發(fā),有一個(gè)接口是最關(guān)鍵的,IDispatch(欲知詳情移步IDispatch接口 - GetIDsOfNames和Invoke)。 IDispatch接口是COM自動(dòng)化的核心。其實(shí),IDispatch這個(gè)接口本身也很簡(jiǎn)單,只有4個(gè)方法:最關(guān)鍵的兩個(gè)方法Invoke和 GetIDsOfNames。腳本語(yǔ)言和編譯型語(yǔ)言之間進(jìn)行通信是通過(guò)IDispatch接口來(lái)行的。下面看一下這個(gè)關(guān)鍵的方法的原型:
IDispatch:public IUnkown
{
//...
HRESULT Invoke( DISPID dispIdMember,REFIID riid, LCID lcid,WORD wFlags,DISPPARAMS FAR* pDispParams,VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr );
//...
}
這個(gè)方法每個(gè)參數(shù)的意義msdn上有詳細(xì)的闡述,就我的理解而看,它作為一個(gè)組件, 向外提供了一個(gè)萬(wàn)能接口,據(jù)此可以實(shí)現(xiàn)兩個(gè)很有用的功能:
1. 獲取和設(shè)置組件的屬性變量. 對(duì)應(yīng)wFlags的DISPATCH_PROPERTYGET和DISPATCH_PROPERTYPUT
2.以任意參數(shù)調(diào)用任意一個(gè)被支持的方法. 對(duì)應(yīng)wFlags的DISPATCH_METHOD
用面向?qū)ο蟮挠^點(diǎn)來(lái)看,有了這兩個(gè)功能,任意一個(gè)實(shí)現(xiàn)該接口的組件就抽象成同一個(gè)接口實(shí)現(xiàn)的萬(wàn)能對(duì)象,可以通過(guò)指定的字符串名字獲取設(shè)置屬性和調(diào)用方 法。如果有過(guò)腳本編程經(jīng)驗(yàn)開(kāi)發(fā)人員一定會(huì)發(fā)現(xiàn),腳本恰恰就是如此。c++書(shū)寫(xiě)代碼的時(shí)候也是通過(guò)名字訪問(wèn)對(duì)象,但編譯好后變成二進(jìn)制代碼后就沒(méi)有名字的概念了,只有偏移和地址。而腳本里頭書(shū)寫(xiě)代碼的時(shí)候是通過(guò)名字,解釋執(zhí)行的時(shí)候也是通過(guò)名字。腳本和native語(yǔ)言的最大區(qū)別是腳本對(duì)象的所有屬性和方法 是動(dòng)態(tài)的,在執(zhí)行的時(shí)候還可以修改。看到這里,很容易聯(lián)想到實(shí)現(xiàn)了IDispatch的組件對(duì)象具有了腳本的特性,c++對(duì)象被腳本化了!這就意味著你可 以把原來(lái)用 c++寫(xiě)的類(lèi)的所有屬性和方法都通過(guò)Invoke來(lái)執(zhí)行,在腳本里頭可以直接訪問(wèn)!相當(dāng)于給腳本增加了native的擴(kuò)展。IDispatch接口很重要 的一個(gè)功能就是如此,微軟通常所說(shuō)的雙接口就是這個(gè)意思。了解了這些,接下來(lái)要和JS腳本交互就比較容易了。
既然是IE內(nèi)核里的JS與C++互相調(diào)用,我們先來(lái)簡(jiǎn)單的了解下IE內(nèi)核編程需要的幾個(gè)常用接口。說(shuō)多了不好理解,先來(lái)看圖。
IWebBrowser2,
IHTMLWindow2,IHTMLDocument2 這三個(gè)常用接口都是從IDispatch
派生的。IWebBrowser2接口里主要提供瀏覽器常規(guī)功能如打開(kāi)URL、前進(jìn)、后退等功能。IHTMLWindow2主要是提供接口操作瀏覽器中打
開(kāi)的window對(duì)象,IHTMLDocument2獲取文檔相關(guān)信息,以及審查和修改HTML元素和文檔中文本,包括獲取JS對(duì)象。
IHTMLWindow2
對(duì)應(yīng)于一個(gè)window的視圖,IHTMLDocument2是IHTMLWindow2渲染文檔,對(duì)應(yīng)著dom樹(shù)結(jié)構(gòu)。在js中有兩個(gè)全局對(duì)象
window和document,分別對(duì)應(yīng)著IHTMlWindow2和 IHTMLDocument2。
想要詳細(xì)了解這些,參看如下資料:
要完成c++和js交互,可以分解成兩個(gè)任務(wù),一是c++調(diào)用js代碼;二是js調(diào)用c++代碼,這其實(shí)也所有腳本和natvie交互的兩個(gè)基本任務(wù)。本文主要根據(jù)自己的理解從設(shè)計(jì)開(kāi)發(fā)的角度去闡述為什么要這么做。
C++調(diào)用JS
每段js執(zhí)行代碼都有它自己的執(zhí)行環(huán)境,在IE里面可以看做是IHTMLWindow2。
根
據(jù)上邊所講,我們用先獲取全局對(duì)象document,從document變成的IDispatch接口中得到
IHTMLDocument2,IHTMLDocument2接口的get_Script方法獲取到了HTML文檔中JS代碼的IDispatch接口,
我們用IDispatch接口,把HTML文檔中的JS代碼當(dāng)作一個(gè)COM對(duì)象,對(duì)他進(jìn)行操作。
CComPtr GetScript()
{
CComPtr spWebBrowser;
HRESULT hResult = QueryControl(IID_IWebBrowser2, (void**)&spWebBrowser);
if (SUCCEEDED(hResult))
{
CComPtr spDocDisp;
hResult = spWebBrowser->get_Document(&spDocDisp);
if (SUCCEEDED(hResult))
{
CComPtr spDocDisp2;
hResult = spDocDisp->QueryInterface(IID_IHTMLDocument2, (void**)&spDocDisp2);
if (SUCCEEDED(hResult))
{
CComPtr spScript;
hResult = spDocDisp2->get_Script(&spScript);
if (SUCCEEDED(hResult))
{
return spScript;
}
}
}
}
}
有兩種方案可以執(zhí)行JS,一種是直接調(diào)用IHTMLWindow2的execScript方法.
HRESULT?execScript(?BSTR?code,? ? BSTR?language,?VARIANT?*pvarRet);
代碼示例:
wstring strJavaScript;
CComVariant pvarRet;
CComBSTR bstrJavaScript(strJavaScript.c_str());
CComBSTR bstrScriptType(_T("javascript"));
CComQIPtr spWindow2(spScriptDisp);
spWindow2->execScript(bstrJavaScript, bstrScriptType, &pvarRet);
要看懂這段代碼不難,我們先來(lái)了解下CComQIPtr,用IDispatch接口調(diào)用COM對(duì)象的各種方法、設(shè)置與獲取COM對(duì)象的屬性、讓COM對(duì)象 回調(diào)我們,都是用IDispatch的Invoke方法來(lái)實(shí)現(xiàn)。一個(gè)Invoke就要實(shí)現(xiàn)那么多功能,用起來(lái)當(dāng)然很麻煩。不過(guò)好在ATL智能指針類(lèi)中的 CComDispatchDriver(即CComQIPtr)封裝了IDispatch接口,使用起來(lái)非常方便!先拿到IHTMLWindow2接口的智能指針,直接把js代碼環(huán)境IDispatch指針的賦值給它。不過(guò)注意這里是BSTR的字符串,可以用 SysAllocString來(lái)分配。
第二種方案同樣是使用IHTMLDocument的get_Script()方法。它能得到一個(gè)IDispatch指針,這個(gè)IDispatch就是IHTMLDocument里的JS。按照前面介紹的IDispatch的使用,你通過(guò)它就可以調(diào)用任意js函數(shù)了。例如要執(zhí)行一個(gè) js中的函數(shù) function。
CComPtr spDocDisp2;
spDocDisp2->get_Script(&spScript);
OLECHAR * Names= L"function" ;
DISPID dispID=0;
//先獲取接受調(diào)度標(biāo)示符DISPID,需要調(diào)用GetIDsOfNames來(lái)獲取
spScript->GetIDsOfNames(IID_NULL,&Names,1,LOCALE_SYSTEM_DEFAULT, &dispID);
//通過(guò)Invoke(援引)方法調(diào)用JS方法
spScript->Invoke(dispID,,IID_NULL ,LOCALE_SYSTEM_DEFAULT,DISPATCH_METHOD,NULL,NULL,NULL,NULL);
這里function是js里面的一個(gè)全局函數(shù)。這里可以看到 Invoke并沒(méi)有直接把字符串名字拿過(guò)來(lái)用,而是通過(guò)另一個(gè)方法GetDispofNames做了一個(gè)映射,獲取接受調(diào)度標(biāo)示符DISPID。通過(guò) IHTMLDocument得到的script接口對(duì)應(yīng)著該頁(yè)面的JS全局環(huán)境,從中可以通過(guò)多次invoke得到任意一個(gè)全局變量,函數(shù),從而能夠得到對(duì)象的成員變量或成員方法。
第二種方案就是通過(guò)Invoke調(diào)用來(lái)實(shí)現(xiàn)在c++中存取js變量和調(diào)用函數(shù)。這和第一種方案的區(qū)別很明顯,一個(gè)是在用c++寫(xiě)js代碼,有點(diǎn)類(lèi)似自己在解析執(zhí)行js了,而前者更簡(jiǎn)單,再?gòu)?fù)雜的js調(diào)用序列,一個(gè)字符串全部搞定。
要做到c++和腳本交互有一個(gè)基本的問(wèn)題要做好,就是腳本中的數(shù)據(jù)類(lèi)型和c++中的數(shù)據(jù)類(lèi)型如何對(duì)應(yīng)起來(lái)。眾所周知,js中有很多類(lèi)型,Boolean, Number, String, Object, Array , Function等。寫(xiě)到這里,插一句,基本所有的語(yǔ)言里頭都有字符串和數(shù)字這兩種基本的數(shù)據(jù)類(lèi)型(c/c++中僅為以\0結(jié)尾的字符數(shù)組),面向?qū)ο蟮?語(yǔ)言中還會(huì)有Object這樣的復(fù)合數(shù)據(jù)類(lèi)型。在Invoke調(diào)用參數(shù)中, VARAINT就代表了c中的基本數(shù)據(jù)類(lèi)型,js中的數(shù)字會(huì)轉(zhuǎn)換成VT_I4或者VT_R4或VT_R8。字符串會(huì)轉(zhuǎn)換成VT_BSTR類(lèi)型的 bstr(這是微軟com標(biāo)準(zhǔn)里使用的字符串類(lèi)型),其他所有的復(fù)合類(lèi)型包括對(duì)象數(shù)組函數(shù)在c中都對(duì)應(yīng)著VT_DISPATCh的一個(gè)IDispatch 指針。有了IDispatch指針,你就可以按照前面的方法任意存取對(duì)象的屬性,也可以發(fā)起函數(shù)調(diào)用并獲得返回值。了解了這些,就可以進(jìn)行c與js的交互 了,它們都通過(guò)IDispatch的invoke調(diào)用來(lái)完成。CComDispatchDriver對(duì)GetIDsOfNames和Invoke進(jìn)一步進(jìn) 行了封裝,只需更少的參數(shù)即方便可調(diào)用。
Invoke0??? //調(diào)用0個(gè)參數(shù)的方法
Invoke1??? //調(diào)用1個(gè)參數(shù)的方法
Invoke2??? //調(diào)用2個(gè)參數(shù)的方法
InvokeN??? //調(diào)用多個(gè)參數(shù)的方法
說(shuō)了這么多,估計(jì)有些人看得云里霧里的。下邊直接給出例子:
我們動(dòng)手寫(xiě)一個(gè)HTML,其中包含這樣一段JS代碼:
function Add(value1, value2) {
return value1 + value2;
}
然后我們用WebBrowser加載這個(gè)HTML后,在VC中這樣來(lái)調(diào)用這個(gè)函數(shù)名為Add的JS函數(shù):
//別忘了#include
//m_WebBrowser是一個(gè)WebBrowser的Activex控件對(duì)象。
CComQIPtr spDoc = m_WebBrowser.get_Document();
CComDispatchDriver spScript;
spDoc->get_Script(&spScript);
CComVariant var1 = 10, var2 = 20, varRet;
spScript.Invoke2(L"Add", &var1, &var2, &varRet);
spScript.Invoke2的作用是調(diào)用JS函數(shù)中名為Add的函數(shù),傳入兩個(gè)參數(shù),用varRet接收返回值。Invoke2調(diào)用成功后,varRet得到了返回值30。
但這樣的話一次只能接受一個(gè)返回值。如果要一次接受多個(gè)返回值的話,怎么辦呢?
我們可以讓JS返回一個(gè)JS中的Array數(shù)組或Object對(duì)象。
當(dāng) JS函數(shù)return一個(gè)Array或一個(gè)Object對(duì)象時(shí),VC這邊的 varRet將接受到一個(gè)代表該對(duì)象的IDispatch接口。我們?nèi)匀挥肅ComDispatchDriver來(lái)管理這個(gè)IDispatch。 CComDispatchDriver有四個(gè)方法:
GetProperty
GetPropertyByName
PutProperty
PutPropertyByName
來(lái)從這個(gè)Array或Object對(duì)象中取出我們要的數(shù)據(jù)。
實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),讓我們?cè)賮?lái)寫(xiě)一個(gè)JS函數(shù):
function Add(value1, value2) {
var array = new Array();
array[0] = value1;
array[1] = value2;
array[2] = value1 + value2;
return array;
}
然后在VC中這樣寫(xiě):
CComQIPtr spDoc = m_WebBrowser.get_Document();
CComDispatchDriver spScript;
spDoc->get_Script(&spScript);
CComVariant var1 = 10, var2 = 20, varRet;
spScript.Invoke2(L"Add", &var1, &var2, &varRet);
CComDispatchDriver spArray = varRet.pdispVal;
//獲取數(shù)組中元素個(gè)數(shù),這個(gè)length在JS中是Array對(duì)象的屬性
CComVariant varArrayLen;
spArray.GetPropertyByName(L"length", &varArrayLen);
//獲取數(shù)組中第0,1,2個(gè)元素的值:
CComVariant varValue[3];
spArray.GetPropertyByName(L"0", &varValue[0]);
spArray.GetPropertyByName(L"1", &varValue[1]);
spArray.GetPropertyByName(L"2", &varValue[2]);
可以看到,10,20,30,這三個(gè)JS函數(shù)返回的值已經(jīng)躺在我們的varValue[3]里了。
當(dāng)然,如果不知道JS返回的Array對(duì)象里面有幾個(gè)元素,我們可以在VC這邊獲取它的length屬性,然后在一個(gè)循環(huán)中取出數(shù)組中的每個(gè)值。
如果我們的JS函數(shù)返回一個(gè)包含有多個(gè)屬性值的Object對(duì)象,VC這邊該如何接收呢?
讓我們?cè)賮?lái)寫(xiě)一個(gè)JS函數(shù):
function Add(value1, value2) {
var data = new Object();
data.result = value1 + value2;
data.str = "Hello,World!";
return data;
}
然后在VC中我們這樣接收:
CComQIPtr spDoc = m_WebBrowser.get_Document();
CComDispatchDriver spScript;
spDoc->get_Script(&spScript);
CComVariant var1 = 10, var2 = 20, varRet;
spScript.Invoke2(L"Add", &var1, &var2, &varRet);
CComDispatchDriver spData = varRet.pdispVal;
CComVariant varValue1, varValue2;
spData.GetPropertyByName(L"result", &varValue1);
spData.GetPropertyByName(L"str", &varValue2);
我 們從JS返回的Object對(duì)象里取出了它的兩個(gè)屬性,result和str,分別是一個(gè)整形數(shù)據(jù)和一個(gè)字符串。這里JS代碼是我們自己寫(xiě)的,在VC這邊 當(dāng)然事先知道這個(gè)JS函數(shù)返回的對(duì)象有result和str這兩個(gè)屬性。如果JS代碼不是我們寫(xiě)的,或者它的屬性是事先不能確定的,該怎么辦呢?答案是使用IDispatchEx接口來(lái)枚舉這個(gè)對(duì)象的相關(guān)信息(方法名、屬性名)。
C++調(diào)用JS的實(shí)例演示到此為止。
js調(diào)用c++代碼
按照前面所說(shuō)的IDispatch的用途,就可以推斷出如何做到這一點(diǎn)了,自定義一個(gè)c++類(lèi),實(shí)現(xiàn)一個(gè)IDispatch的接口,把它的指針通過(guò)某次 js調(diào)用作為返回值返回給js,那么js代碼中就持有該對(duì)象了,就可以像使用普通js對(duì)象一樣的使用它。問(wèn)題是,一開(kāi)始js啥都沒(méi)有,怎么直接調(diào)到c++ 里頭從而返回c++對(duì)象呢?IE已經(jīng)考慮好了這個(gè)問(wèn)題,它對(duì)于每個(gè)IWebbrowser2實(shí)例(頂層)有一個(gè)內(nèi)置的IDispatch對(duì)象,該對(duì)象可以 在創(chuàng)建瀏覽器控件實(shí)例之后在c++中自己制定,而在js中則使用window.external來(lái)訪問(wèn)。也就是說(shuō)每個(gè)js環(huán)境都已經(jīng)內(nèi)置了一個(gè)全局對(duì)象 external,并且它對(duì)應(yīng)的c++中的IDispatch可以由程序員自己指定。下面談一下如何來(lái)設(shè)置這個(gè)對(duì)象實(shí)例。
在windows中要自己host一個(gè)active控件,如果用sdk自己寫(xiě)。其中有一個(gè)接口叫IDocHostUIHandler ,它有一個(gè)方法GetExternalDisp用以向宿主查詢(xún)一個(gè)IDispatch對(duì)象,就直接對(duì)應(yīng)著js中的external腳本對(duì)象。 IDocHostUIHandler 還有一個(gè)有用的方法ShowContextMenu,當(dāng)要show菜單的時(shí)候這個(gè)方法會(huì)被回調(diào),應(yīng)用程序就可以自定義菜單了。MFC也可以很方便的 host一個(gè)IE控件,但它的類(lèi)庫(kù)太龐大了,幸虧微軟又出了ATL,提供了一個(gè)輕量級(jí)的方法讓你可以達(dá)到同樣的效果。下面直接貼代碼片段.
class CWebBrowser : public CAxHostWindow
{
private:
CComPtr m_pWebBrowser; //保存創(chuàng)建出來(lái)的瀏覽器控件實(shí)例
BEGIN_MSG_MAP(CWebBrowser)
MESSAGE_HANDLER(WM_CREATE,OnCreate)
CHAIN_MSG_MAP(CAxHostWindow)
END_MSG_MAP()
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
// Create WebBrowser object
LPOLESTR pName=NULL;
StringFromCLSID(CLSID_WebBrowser,&pName);
CComPtrdisp;
CComPtr p;
_InternalQueryInterface(IID_IDispatch,(void**)&disp);
// 創(chuàng)建 WebBrowser
CreateControlEx(pName,m_hWnd,NULL,&p,DIID_DWebBrowserEvents2,disp);
CoTaskMemFree(pName);
// 查詢(xún)IWebBrowser2 接口,用于控制
HRESULT hRet = QueryControl(IID_IWebBrowser2, (void**)&this->m_pWebBrowser);
return m_pWebBrowser?S_OK:-1;
}
}
CWebBrowser 是用戶自己的宿主窗口,在它的OnCreate里頭創(chuàng)建com對(duì)象,一個(gè)瀏覽器窗口就出來(lái)了,這個(gè)代碼是不是很簡(jiǎn)潔?CAxHostWindow為我們做 了很多事情,包括IDocHostUIHandler也被實(shí)現(xiàn),所以我們從它派生就天然的擁有了很多控制IE控件的能力,當(dāng)然都是通過(guò)com接口來(lái)完成 的。以后如果有定制需求,大可重寫(xiě)父類(lèi)的虛函數(shù)來(lái)達(dá)到目的。CAxHostWindow還封裝了一個(gè)方法SetExternalDispatch,到這里 一切都可以暫時(shí)告一段落了,你可以在CWebBrowser中實(shí)現(xiàn)IDispatch也可以單獨(dú)用一個(gè)類(lèi)來(lái)實(shí)現(xiàn),然后把IDispatch接口設(shè)進(jìn)去就可 以了。有興趣研究這個(gè)寄宿控件過(guò)程的童鞋們可以看CAxHostWindow的代碼實(shí)現(xiàn),全在一個(gè)頭文件中。
假設(shè)你的external提供了一個(gè)函數(shù)創(chuàng)建對(duì)象 ? function newMyObject,在js中
var newObject=window.external.newMyObject(); //通過(guò)external構(gòu)建一個(gè)c++對(duì)象交給js持有
alert(newObject.name); ? ? ? //訪問(wèn)該對(duì)象的屬性
alert(newObject.GetValue()) ?//調(diào)用該對(duì)象的方法
那么你需要做的事情其實(shí)還是關(guān)注Invoke就可以了.在external的IDispatch的Invoke實(shí)現(xiàn)中
STDMETHODIMP CWebBrowserDisp::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, unsigned int* puArgErr)
{
HRESULT nRet = S_OK;
if(wFlags&DISPATCH_METHOD) //屬于方法調(diào)用
{
//給newMyObject分配的id,字符串名字映射
if(dispIdmember== DISPID_newMyObject)
{
IDispatch* pMyObject=NULL;
//創(chuàng)建c++對(duì)象并獲取其IDispatch接口
CreateMyObject(&pMyObject);
pVarResult->vt=VT_DISPATCH;
pVarResult->pdispVal=pMyObject; //作為返回值傳遞給js
}
}
return 0;
}
這個(gè)代碼也很簡(jiǎn)潔。據(jù)此可以看出,要把c++對(duì)象導(dǎo)出到j(luò)s中,那么該對(duì)象必須要實(shí)現(xiàn)IDispatch接口,只需要把這個(gè)接口作為Invoke的返回值 傳給js即可。它有引用計(jì)數(shù),不必?fù)?dān)心內(nèi)存的釋放問(wèn)題,在js的垃圾回收被觸發(fā)的某個(gè)時(shí)刻自然會(huì)被銷(xiāo)毀。接下來(lái),MyObject有哪些屬性和方法可以被 js調(diào)用,那就又歸它自己的IDispatch的Invoke實(shí)現(xiàn)來(lái)關(guān)心了。
另外一種就是在webbrowser控件中,JS調(diào)用C++方法。如果你對(duì)webbrowser控件熟悉的話,這里使用起來(lái)就更簡(jiǎn)單了。Invoke接口實(shí)現(xiàn)基本上和上邊的類(lèi)似,唯一不同的是如何讓JS調(diào)用到本地的C++ 代碼。在JS代碼中創(chuàng)建了函數(shù)window.external.newMyObject()。頁(yè)面渲染時(shí),會(huì)觸發(fā)瀏覽器的GETEXTERNAL事件,在瀏覽器中,通過(guò)消息過(guò)濾,當(dāng)消息為WN_GETEXTERNAL時(shí),通過(guò)IDispatch接口,獲取JS代碼需要調(diào)用的類(lèi)。
IDispatch **ppDispatch = (IDispatch**)wParam;
*ppDispatch = &m_superCall;
綜上所述,在IE中和c++與js交互,IDispatch扮演了很重要的角色,理解好了它你就可以隨心所欲的c++和js的混合編程了。COM接口很不容易理 解,知道怎么用,卻難以了解其內(nèi)部機(jī)制。其實(shí),在前面所講的過(guò)程中,IDispatch是自己的代碼創(chuàng)建的,和系統(tǒng)完全無(wú)關(guān)。從c++的語(yǔ)法看,它就是繼 承了一個(gè)虛基類(lèi),實(shí)現(xiàn)其全部方法而已,還有就是引用計(jì)數(shù)。所以,我們完全可以用很簡(jiǎn)單的c++代碼來(lái)寫(xiě)自己的IDispatch,不必去理會(huì)那么多的COM特性。js執(zhí)行環(huán)境總是在主線程,所以你要知道一點(diǎn)你的對(duì)象的方法也總是在主線程被調(diào)用。下邊給出簡(jiǎn)單的實(shí)現(xiàn)代碼:
#include "StdAfx.h"
#include "SQSuperCall.h"
CJSCallC::CJSCallC(void)
{
m_mapFunction[TEXT("FuncTest")] = DISPID_FuncTest;
}
CJSCallC::~CJSCallC(void)
{
}
HRESULT STDMETHODCALLTYPE CJSCallC::GetIDsOfNames(
/* [in] */ __RPC__in REFIID riid,
/* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
/* [range][in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId)
{
HRESULT hr = NOERROR;
for (UINT nIndex = 0; nIndex < cNames; ++nIndex)
{
wstring strFuntion = rgszNames[nIndex];
map::iterator iter = m_mapFunction.find(strFuntion);
if (m_mapFunction.end() != iter)
{
rgDispId[nIndex] = iter->second;
}
else
{
hr = ResultFromScode(DISP_E_UNKNOWNNAME);
rgDispId[nIndex] = DISPID_UNKNOWN;
}
}
return hr;
}
/* [local] */ HRESULT STDMETHODCALLTYPE CJSCallC::Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr)
{
if (dispIdMember == DISPID_FuncTest)
{
int paramsCount = pDispParams->cArgs;
if (paramsCount < 2)
return S_FALSE;
VARIANTARG* cmdVar = (VARIANTARG*)(&pDispParams->rgvarg[paramsCount - 1]);
if (!(cmdVar->vt == VT_I4 || cmdVar->vt == VT_BSTR))
return S_FALSE;
int nCmdId = cmdVar->intVal;
cmdVar = (VARIANTARG*)(&pDispParams->rgvarg[paramsCount - 2]);
if( cmdVar->vt != VT_BSTR )
return S_FALSE;
CString csInfos = cmdVar->bstrVal;
wstring strInfos(csInfos);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CJSCallC::QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */
__RPC__deref_out void **ppvObject)
{
//*ppvObject = NULL;
if (riid == IID_IUnknown)
{
*ppvObject = static_cast(this);
}
else if (riid == IID_IDispatch)
{
*ppvObject = static_cast(this);
}
else
{
return E_NOINTERFACE;
}
return S_OK;
}
參考文檔:
總結(jié)
以上是生活随笔為你收集整理的cef js返回c++的代码_CEF3开发者系列之外篇——IE中JS与C++交互的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 视频综合平台系统架构分析-1
- 下一篇: 分享视频分析软件常用的几个C++库