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);
程序员的基础教程:菜鸟程序员