第一步解決了邊框和上下文菜單問題,第二部就是要解決c++程序和html頁面交互的問題。最開始的想法是通過c++去更新頁面內容的方式來完成c++->html的通訊,通過BeforeNavigate2 接口,截獲頁面url地址的方式來完成html->c++的通訊。但是這種方式存在以下缺點:
(1) c++->html 的問題在于導致c++代碼復雜,需要通過c++代碼來完成頁面生成,如果修改頁面,將產生很大的工作量。雖然嘗試用了模板方法解決,但是還是比較繁瑣,而且會導致經常通訊的時候,頁面會經常刷新,產生其他的一些問題。
(2) html->c++ 的問題在于 傳遞參數不方便,解析也不方便、無法獲取返回值、腳本中要調用不方便
為了解決這些問題,經過google后找到了問題的解決辦法 :
(1) c++->html ,可以通過調用頁面腳本方法來實現,調用方法如下:
wxVariant wxIEHtmlWin::ExecScript(const wxString &fun,const std::vector<wxString> ¶ms )


{
wxVariant result(false);
if (! m_webBrowser.Ok())
return result;

// get document dispatch interface
IDispatch *iDisp = NULL;
HRESULT hr = m_webBrowser->get_Document(&iDisp);
if (hr != S_OK)
return result;

// Query for Document Interface
wxAutoOleInterface<IHTMLDocument2> hd(IID_IHTMLDocument2, iDisp);
iDisp->Release();

if (! hd.Ok())
return result;

IDispatch *spScript;
hr = hd->get_Script(&spScript);

if(FAILED(hr))
return result;
BSTR bstrMember = wxConvertStringToOle(fun);
DISPID dispid = NULL;
hr = spScript->GetIDsOfNames(IID_NULL,&bstrMember,1,
LOCALE_SYSTEM_DEFAULT,&dispid);
if(FAILED(hr))

{
return result;
}
//Putting parameters
DISPPARAMS dispparams;
memset(&dispparams, 0, sizeof dispparams);
dispparams.cArgs = params.size();
dispparams.rgvarg = new VARIANT[dispparams.cArgs];
dispparams.cNamedArgs = 0;

for( int i = 0; i < params.size(); i++)

{
CComBSTR bstr = wxConvertStringToOle(params[params.size() - 1 - i]);
// back reading
bstr.CopyTo(&dispparams.rgvarg[i].bstrVal);

dispparams.rgvarg[i].vt = VT_BSTR;
}
EXCEPINFO excepInfo;
memset(&excepInfo, 0, sizeof excepInfo);
VARIANT varRet;
UINT nArgErr = (UINT)-1; // initialize to invalid arg
//Call JavaScript function
hr = spScript->Invoke(dispid,IID_NULL,0,
DISPATCH_METHOD,&dispparams,
&varRet,&excepInfo,&nArgErr);
delete [] dispparams.rgvarg;
if(FAILED(hr))

{
return result;
}

wxConvertOleToVariant(varRet,result);
return result;


}
這個方法實現了C++對頁面腳本調用,而且參數個數可以任意。比如頁面腳本是 :
C++中的調用方法是 :
std::vector<wxString> params;
params.push_back("a");
params.push_back("b");
params.push_back("c");
xxx->ExecScripts("fun",params);
還可以獲得腳本返回的結果。
(2) html->c++ 通過腳本的window.external 方法,首先,在前文提到過的IDocHostUIHandler 接口中,實現方法:
HRESULT STDMETHODCALLTYPE FrameSite::GetExternal(IDispatch **ppDispatch)


{
IDispatch * pDisp = m_window->getExternal();
if(pDisp)

{
pDisp->AddRef();
*ppDispatch = pDisp;
}

return S_OK;
}
其中 m_window->getExternal();
返回的是自定義的一個IDispatch 接口類:

/**//*
* IDispimp.H
* IDispatch
*
* Copyright (c)1995-1999 Microsoft Corporation, All Rights Reserved
*/


#ifndef _IDISPIMP_H_
#define _IDISPIMP_H_
#include <oaidl.h>
class CustomFunction;
class CImpIDispatch : public IDispatch


{
protected:
ULONG m_cRef;

public:
CImpIDispatch(void);
~CImpIDispatch(void);

STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);

//IDispatch
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo);

STDMETHODIMP GetTypeInfo(/**//* [in] */ UINT iTInfo,

/**//* [in] */ LCID lcid,

/**//* [out] */ ITypeInfo** ppTInfo);
STDMETHODIMP GetIDsOfNames(

/**//* [in] */ REFIID riid,

/**//* [size_is][in] */ LPOLESTR *rgszNames,

/**//* [in] */ UINT cNames,

/**//* [in] */ LCID lcid,

/**//* [size_is][out] */ DISPID *rgDispId);
STDMETHODIMP 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);


void setCustomFunction(CustomFunction *fun)
{m_fun = fun;}
private:
CustomFunction *m_fun;

};
#endif //_IDISPIMP_H_

主要實現以下兩個方法:

wxString cszCB_CustomFunction = wxT("CB_CustomFunction");
#define DISPID_CB_CustomFunction 3
STDMETHODIMP CImpIDispatch::GetIDsOfNames(

/**//* [in] */ REFIID riid,

/**//* [size_is][in] */ OLECHAR** rgszNames,

/**//* [in] */ UINT cNames,

/**//* [in] */ LCID lcid,

/**//* [size_is][out] */ DISPID* rgDispId)


{
HRESULT hr;
UINT i;

// Assume some degree of success
hr = NOERROR;



for ( i=0; i < cNames; i++)
{
wxString cszName = rgszNames[i];
if(cszName == cszCB_CustomFunction)

{
rgDispId[i] = DISPID_CB_CustomFunction;
}

else
{
// One or more are unknown so set the return code accordingly
hr = ResultFromScode(DISP_E_UNKNOWNNAME);
rgDispId[i] = DISPID_UNKNOWN;
}
}
return hr;
}

STDMETHODIMP CImpIDispatch::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_CB_CustomFunction)

{
if(wFlags & DISPATCH_PROPERTYGET)

{
if(pVarResult != NULL)

{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )

{
//arguments come in reverse order
//for some reason
if(!m_fun) return S_OK;

wxString arg1,arg2;
if(pDispParams->cArgs<1) return S_FALSE;
wxString cmd = pDispParams->rgvarg[pDispParams->cArgs-1].bstrVal;
std::vector<wxString> args;
if(pDispParams->cArgs>1)

{
for(int i=pDispParams->cArgs-2;i>=0;i--)
args.push_back(pDispParams->rgvarg[i].bstrVal);
}
wxString re = m_fun->execute(cmd,args);
if(pVarResult != NULL)

{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BSTR;
wxVariant wVar(re);
VariantToMSWVariant(wVar,*pVarResult);
}
}
}


return S_OK;
}
其中 CustomFunction 定義如下:
#pragma once
#include <wx/wx.h>
#include <vector>
class CustomFunction


{
public:

CustomFunction(void)

{
}

virtual ~CustomFunction(void)

{
}

virtual wxString execute(const wxString &cmd, const std::vector<wxString> &args) = 0;
};

然后只要在自己類里面繼承這個接口,就可以接收來之腳本的調用請求。
腳本里面編寫函數:
window._callFun = function()

{
var fun = "window.external.CB_CustomFunction(";
for(i=0;i<arguments.length;i++)

{
if(i!=0)
fun = fun+",";
fun = fun+"\""+arguments[i]+"\"";
}
fun = fun+")";
//alert(fun);
return (eval(fun));
}
然后調用的地方寫:
_callFun("fun","param1","param2",
);
就可以調用c++的函數,并且可以得到返回值,從而解決了html->c++的通訊問題
解決了雙向通訊后,頁面就不需要用刷新來解決,網頁設計師和c++編程人員只要定義好通訊接口,大家各自實現好接口方法就可以完成界面功能了。