第一步解决了边框和上下文菜单问题,第二部就是要解决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++编程人员只要定义好通讯接口,大家各自实现好接口方法就可以完成界面功能了。