WebBrowser-Javascript與C++互操做

WebBrowser控件是Microsoft提供的一個用於網頁瀏覽的客戶端控件,WebBrowser控件的使用至關普遍,例如不少郵件客戶端都是使用可編輯的WebBrowser控件做爲寫郵件的工具,也有不少軟件用WebBrowser控件彈出網頁,如qq的個性首頁。關於WebBrowser的應用,也能夠參考筆者開發的開源WebIM,Lesktop開源WebIM提供的IM客戶端就是使用WebBrowser實現的:javascript

 

微軟的MFC和.NET都有WebBrowser控件,這兩個控件雖然容易上手,不過因爲包裝的太好,因此很難深刻。所以本文介紹的WebBrowser將不使用MFC和.NET,而是使用C++實現SDK的WebBrowser。java

因爲本文主要探討如何實現JavaScript與C++的互操做,對於如何使用SDK實現WebBrowser,本文不作詳細介紹,讀者能夠參考如下這篇文章:程序員

http://blog.csdn.net/norsd/archive/2008/09/13/2921389.aspx函數

不過儘管文章中介紹了SDK實現WebBrowser的要點,卻沒有提供一個能夠運行的示例,若是要看到實際的運行效果,能夠下載如下這份源代碼,源代碼中也包括了互操做的演示:工具

SDK實現WebBrowser及演示代碼 [好文,推薦一下看不懂]
一、C++調用WebBrowser中的全局函數,變量等this

(1) 從C++的角度看WebBrowser中的對象spa

WebBrowser中的對象大體能夠分紅兩類:DOM對象和使用Javascript建立的對象。可是不管是那種對象,從C++的角度來看,都是一些實現了IDispatch接口的對象,所以,若是用C++操做WebBrowser中的對象(全局函數,變量,DOM)等,只須要經過IDispatch便可。.net

(2) 3個經常使用的函數orm

當獲取了WebBrowser的對象的IDispatch接口後,就能夠調用IDispatch的Invoke方法來調用對象的方法,獲取對象的屬性和設置對象的屬性。可是Invoke是經過ID判斷要調用指定對象的哪個方法(或屬性),所以在經過方法(或屬性)名稱調用對象的方法是,必須先調用IDispatch的GetIDsOfNames方法,將方法(或屬性)名轉換成ID,而後才能經過IDispatch的Invoke方法調用對象的方法。爲了方便操做,封裝了三個函數,分別用於調用WebBrowser的對象的方法,讀取對象的屬性,設置對象的屬性。對象

DISPID CWebBrowserBase::FindId(IDispatch *pObj, LPOLESTR pName)
{
    DISPID id = 0;
    if(FAILED(pObj->GetIDsOfNames(IID_NULL,&pName,1,LOCALE_SYSTEM_DEFAULT,&id))) id = -1;
    return id;
}

HRESULT CWebBrowserBase::InvokeMethod(IDispatch *pObj, LPOLESTR pName, VARIANT *pVarResult, VARIANT *p, int cArgs)
{
    DISPID dispid = FindId(pObj, pName);
    if(dispid == -1) return E_FAIL;

    DISPPARAMS ps;
    ps.cArgs = cArgs;
    ps.rgvarg = p;
    ps.cNamedArgs = 0;
    ps.rgdispidNamedArgs = NULL;

    return pObj->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &ps, pVarResult, NULL, NULL);
}

HRESULT CWebBrowserBase::GetProperty(IDispatch *pObj, LPOLESTR pName, VARIANT *pValue)
{
    DISPID dispid = FindId(pObj, pName);
    if(dispid == -1) return E_FAIL;

    DISPPARAMS ps;
    ps.cArgs = 0;
    ps.rgvarg = NULL;
    ps.cNamedArgs = 0;
    ps.rgdispidNamedArgs = NULL;

    return pObj->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &ps, pValue, NULL, NULL);
}

HRESULT CWebBrowserBase::SetProperty(IDispatch *pObj, LPOLESTR pName, VARIANT *pValue)
{
    DISPID dispid = FindId(pObj, pName);
    if(dispid == -1) return E_FAIL;

    DISPPARAMS ps;
    ps.cArgs = 1;
    ps.rgvarg = pValue;
    ps.cNamedArgs = 0;
    ps.rgdispidNamedArgs = NULL;

    return pObj->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &ps, NULL, NULL, NULL);
}(3)調用頁面的全局函數

在網頁中,全部的全局函數均是window的一個方法,所以,若是要調用全局函數,首先要獲取到頁面的window對象,而後用InvokeMethod調用全局函數,例如,假設頁面中有一個Test全局函數:

<script language="javascript" type="text/javascript">
function Test()
{
    alert("你調用了Test");
}
</script>那麼,您能夠在C++中用如下代碼調用Test函數:

VARIANT params[10];
VARIANT ret;
//獲取頁面window
IDispatch *pHtmlWindow = pBrowser->GetHtmlWindow();
//頁面全局函數Test其實是window的Test方法,
CWebBrowserBase::InvokeMethod(pHtmlWindow, L"Test", &ret, params, 0);(4)調用全局對象的方法

在網頁中,全部的全局變量均是window的一個屬性,所以,若是要調用變量的方法(或屬性),首先要獲取到頁面的window對象,而後用GetProperty獲取到全局變量,而後就能夠調用這個對象的方法,或讀寫其屬性。例如,假設頁面中有一個globalObject全局變量:

<script language="javascript" type="text/javascript">
function GlobalObject()
{
    this.Test=function()
    {
        alert("你調用了GlobalObject.Test");
    }
}

var globalObject = new GlobalObject();
</script>那麼,您能夠使用一下代碼調用globalObject的Test方法:

VARIANT params[10];
VARIANT ret;
//獲取頁面window
IDispatch *pHtmlWindow = pBrowser->GetHtmlWindow();
//獲取globalObject
CVariant globalObject;
params[0].vt = VT_BSTR;
params[0].bstrVal = L"globalObject";
CWebBrowserBase::GetProperty(pHtmlWindow, L"globalObject", &globalObject);
//調用globalObject.Test
CWebBrowserBase::InvokeMethod(globalObject.pdispVal, L"Test", &ret, params, 0);二、在網頁中調用客戶端的方法

上文咱們已經介紹瞭如何在C++中調用WebBrowser中的對象,接下來,將介紹如何在頁面中調用客戶端中的函數和對象:

(1) 經過window.external調用

下面將示例如何經過window.external調用客戶端中的函數,假設在C++中定義了一個名爲CppCall的函數:

void CppCall()
{
    MessageBox(NULL, L"您調用了CppCall", L"提示(C++)", 0);
}定義一個對象,而且實現IDispatch接口:

class ClientCall:public IDispatch
{
    long _refNum;
public:
    ClientCall()
    {
        _refNum = 1;
    }
    ~ClientCall(void)
    {
    }
public:

    // IUnknown Methods

    STDMETHODIMP QueryInterface(REFIID iid,void**ppvObject)
    {
        *ppvObject = NULL;
        if (iid == IID_IUnknown)    *ppvObject = this;
        else if (iid == IID_IDispatch)    *ppvObject = (IDispatch*)this;
        if(*ppvObject)
        {
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return ::InterlockedIncrement(&_refNum);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        ::InterlockedDecrement(&_refNum);
        if(_refNum == 0)
        {
            delete this;
        }
        return _refNum;
    }

    // IDispatch Methods

    HRESULT _stdcall GetTypeInfoCount(
        unsigned int * pctinfo) 
    {
        return E_NOTIMPL;
    }

    HRESULT _stdcall GetTypeInfo(
        unsigned int iTInfo,
        LCID lcid,
        ITypeInfo FAR* FAR* ppTInfo) 
    {
        return E_NOTIMPL;
    }

    HRESULT _stdcall GetIDsOfNames(
        REFIID riid, 
        OLECHAR FAR* FAR* rgszNames, 
        unsigned int cNames, 
        LCID lcid, 
        DISPID FAR* rgDispId 
    )
    {
        if(lstrcmp(rgszNames[0], L"CppCall")==0)
        {
            //網頁調用window.external.CppCall時,會調用這個方法獲取CppCall的ID
            *rgDispId = 100;
        }
        return S_OK;
    }

    HRESULT _stdcall Invoke(
        DISPID dispIdMember,
        REFIID riid,
        LCID lcid,
        WORD wFlags,
        DISPPARAMS* pDispParams,
        VARIANT* pVarResult,
        EXCEPINFO* pExcepInfo,
        unsigned int* puArgErr
    )
    {
        if(dispIdMember == 100)
        {
            //網頁調用CppCall時,或根據獲取到的ID調用Invoke方法
            CppCall();
        }
        return S_OK;
    }
};定義類ClientCall後,就能夠建立一個ClientCall的對象,傳遞給WebBrowser,使得網頁中能夠經過window.external調用CppCall,要實現這些功能,WebBrowser須要實現IDocHostUIHandler接口,並重寫GetExternal方法以返回一個ClientCall對象:

ClientCall *pClientCall;
pClientCall = new ClientCall();

virtual HRESULT STDMETHODCALLTYPE GetExternal(IDispatch **ppDispatch)
{
    //重寫GetExternal返回一個ClientCall對象
    *ppDispatch = pClientCall;
    return S_OK;
}接下來,就能夠在網頁中調用了:

window.external.CppCall()(2)向網頁傳遞迴調函數

向網頁傳遞迴調函數的一個典型應用就是在客戶端中用C++處理DOM的事件(例如,處理按鈕的onclick事件),這裏要注意的是,與C++不一樣的是,在網頁中,所謂的函數,其實就是一個具備call方法的對象,所以,向網頁傳遞一個回調函數,其實就是傳遞一個實現了call方法的對象,所以,咱們必須定義一個C++類,並實現IDispatch接口:

typedef void _stdcall JsFunction_Callback(LPVOID pParam);

class JsFunction:public IDispatch
{
    long _refNum;
    JsFunction_Callback *m_pCallback;
    LPVOID m_pParam;
public:
    JsFunction(JsFunction_Callback *pCallback, LPVOID pParam)
    {
        _refNum = 1;
        m_pCallback = pCallback;
        m_pParam = pParam;
    }
    ~JsFunction(void)
    {
    }
public:

    // IUnknown Methods

    STDMETHODIMP QueryInterface(REFIID iid,void**ppvObject)
    {
        *ppvObject = NULL;

        if (iid == IID_IOleClientSite)    *ppvObject = (IOleClientSite*)this;
        else if (iid == IID_IUnknown)    *ppvObject = this;
        if(*ppvObject)
        {
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return ::InterlockedIncrement(&_refNum);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        ::InterlockedDecrement(&_refNum);
        if(_refNum == 0)
        {
            delete this;
        }
        return _refNum;
    }

    // IDispatch Methods

    HRESULT _stdcall GetTypeInfoCount(
        unsigned int * pctinfo) 
    {
        return E_NOTIMPL;
    }

    HRESULT _stdcall GetTypeInfo(
        unsigned int iTInfo,
        LCID lcid,
        ITypeInfo FAR* FAR* ppTInfo) 
    {
        return E_NOTIMPL;
    }

    HRESULT _stdcall GetIDsOfNames(
        REFIID riid, 
        OLECHAR FAR* FAR* rgszNames, 
        unsigned int cNames, 
        LCID lcid, 
        DISPID FAR* rgDispId 
    )
    {
        //使人費解的是,網頁調用函數的call方法時,沒有調用GetIDsOfNames獲取call的ID,而是直接調用Invoke
        return E_NOTIMPL;
    }

    HRESULT _stdcall Invoke(
        DISPID dispIdMember,
        REFIID riid,
        LCID lcid,
        WORD wFlags,
        DISPPARAMS* pDispParams,
        VARIANT* pVarResult,
        EXCEPINFO* pExcepInfo,
        unsigned int* puArgErr
    )
    {
        m_pCallback(m_pParam);
        return S_OK;
    }
};接下來,咱們就能夠使用JsFunction向網頁傳遞迴調,如下代碼用於處理按鈕的onclick事件:

static void _stdcall button1_onclick(LPVOID pParam)
{
    MainForm *pMainForm = (MainForm*)pParam;
    MessageBox(pMainForm->hWnd, L"您點擊了button1", L"提示(C++)", 0);
}

VARIANT params[10];

//獲取window
IDispatch *pHtmlWindow = GetHtmlWindow();

//獲取document
CVariant document;
params[0].vt = VT_BSTR;
params[0].bstrVal = L"document";
GetProperty(pHtmlWindow, L"document", &document);

//獲取button1
CVariant button1;
params[0].vt = VT_BSTR;
params[0].bstrVal = L"button1";
InvokeMethod(document.pdispVal, L"getElementById", &button1, params, 1);

//處理button1的onclick事件
params[0].vt = VT_DISPATCH;
params[0].pdispVal = new JsFunction(button1_onclick, this);
SetProperty(button1.pdispVal, L"onclick", params);

 

程序員的基礎教程:菜鳥程序員

相關文章
相關標籤/搜索