WebBrowser介绍——Javascript与C++互操作

WebBrowser控件是Microsoft提供的一个用于网页浏览的客户端控件,WebBrowser控件的使用相当广泛,例如很多邮件客户端都是使用可编辑的WebBrowser控件作为写邮件的工具,也有很多软件用WebBrowser控件弹出网页,如qq的个性首页。关于WebBrowser的应用,也可以参考笔者开发的开源WebIM,Lesktop开源WebIM提供的IM客户端就是使用WebBrowser实现的:

image

微软的MFC和.NET都有WebBrowser控件,这两个控件虽然容易上手,不过由于包装的太好,所以很难深入。因此本文介绍的WebBrowser将不使用MFC和.NET,而是使用C++实现SDK的WebBrowser。

由于本文主要探讨如何实现Javascript与C++的互操作,对于如何使用SDK实现WebBrowser,本文不做详细介绍,读者可以参考以下这篇文章:

使用C++实现SDK之WebBrowser容器_c++ webbrowser dopageaction-CSDN博客

不过尽管文章中介绍了SDK实现WebBrowser的要点,却没有提供一个可以运行的示例,如果要看到实际的运行效果,可以下载以下这份源代码,源代码中也包括了互操作的演示:

SDK实现WebBrowser及演示代码 [好文,推荐一下看不懂]

1、C++调用WebBrowser中的全局函数,变量等

(1) 从C++的角度看WebBrowser中的对象

WebBrowser中的对象大致可以分成两类:DOM对象和使用Javascript创建的对象。但是无论是那种对象,从C++的角度来看,都是一些实现了IDispatch接口的对象,因此,如果用C++操作WebBrowser中的对象(全局函数,变量,DOM)等,只需要通过IDispatch即可。

(2) 3个常用的函数

当获取了WebBrowser的对象的IDispatch接口后,就可以调用IDispatch的Invoke方法来调用对象的方法,获取对象的属性和设置对象的属性。但是Invoke是通过ID判断要调用指定对象的哪一个方法(或属性),因此在通过方法(或属性)名称调用对象的方法是,必须先调用IDispatch的GetIDsOfNames方法,将方法(或属性)名转换成ID,然后才能通过IDispatch的Invoke方法调用对象的方法。为了方便操作,封装了三个函数,分别用于调用WebBrowser的对象的方法,读取对象的属性,设置对象的属性。

 
  1. DISPID CWebBrowserBase::FindId(IDispatch *pObj, LPOLESTR pName)

  2. {

  3. DISPID id = 0;

  4. if(FAILED(pObj->GetIDsOfNames(IID_NULL,&pName,1,LOCALE_SYSTEM_DEFAULT,&id))) id = -1;

  5. return id;

  6. }

  7. HRESULT CWebBrowserBase::InvokeMethod(IDispatch *pObj, LPOLESTR pName, VARIANT *pVarResult, VARIANT *p, int cArgs)

  8. {

  9. DISPID dispid = FindId(pObj, pName);

  10. if(dispid == -1) return E_FAIL;

  11. DISPPARAMS ps;

  12. ps.cArgs = cArgs;

  13. ps.rgvarg = p;

  14. ps.cNamedArgs = 0;

  15. ps.rgdispidNamedArgs = NULL;

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

  17. }

  18. HRESULT CWebBrowserBase::GetProperty(IDispatch *pObj, LPOLESTR pName, VARIANT *pValue)

  19. {

  20. DISPID dispid = FindId(pObj, pName);

  21. if(dispid == -1) return E_FAIL;

  22. DISPPARAMS ps;

  23. ps.cArgs = 0;

  24. ps.rgvarg = NULL;

  25. ps.cNamedArgs = 0;

  26. ps.rgdispidNamedArgs = NULL;

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

  28. }

  29. HRESULT CWebBrowserBase::SetProperty(IDispatch *pObj, LPOLESTR pName, VARIANT *pValue)

  30. {

  31. DISPID dispid = FindId(pObj, pName);

  32. if(dispid == -1) return E_FAIL;

  33. DISPPARAMS ps;

  34. ps.cArgs = 1;

  35. ps.rgvarg = pValue;

  36. ps.cNamedArgs = 0;

  37. ps.rgdispidNamedArgs = NULL;

  38. return pObj->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &ps, NULL, NULL, NULL);

  39. }

(3)调用页面的全局函数

在网页中,所有的全局函数均是window的一个方法,因此,如果要调用全局函数,首先要获取到页面的window对象,然后用InvokeMethod调用全局函数,例如,假设页面中有一个Test全局函数:

 
  1. <script language="javascript" type="text/javascript">

  2. function Test()

  3. {

  4. alert("你调用了Test");

  5. }

  6. </script>

那么,您可以在C++中用以下代码调用Test函数:

 
  1. VARIANT params[10];

  2. VARIANT ret;

  3. //获取页面window

  4. IDispatch *pHtmlWindow = pBrowser->GetHtmlWindow();

  5. //页面全局函数Test实际上是window的Test方法,

  6. CWebBrowserBase::InvokeMethod(pHtmlWindow, L"Test", &ret, params, 0);

(4)调用全局对象的方法

在网页中,所有的全局变量均是window的一个属性,因此,如果要调用变量的方法(或属性),首先要获取到页面的window对象,然后用GetProperty获取到全局变量,然后就可以调用这个对象的方法,或读写其属性。例如,假设页面中有一个globalObject全局变量:

 
  1. <script language="javascript" type="text/javascript">

  2. function GlobalObject()

  3. {

  4. this.Test=function()

  5. {

  6. alert("你调用了GlobalObject.Test");

  7. }

  8. }

  9. var globalObject = new GlobalObject();

  10. </script>

那么,您可以使用一下代码调用globalObject的Test方法:

 
  1. VARIANT params[10];

  2. VARIANT ret;

  3. //获取页面window

  4. IDispatch *pHtmlWindow = pBrowser->GetHtmlWindow();

  5. //获取globalObject

  6. CVariant globalObject;

  7. params[0].vt = VT_BSTR;

  8. params[0].bstrVal = L"globalObject";

  9. CWebBrowserBase::GetProperty(pHtmlWindow, L"globalObject", &globalObject);

  10. //调用globalObject.Test

  11. CWebBrowserBase::InvokeMethod(globalObject.pdispVal, L"Test", &ret, params, 0);

2、在网页中调用客户端的方法

上文我们已经介绍了如何在C++中调用WebBrowser中的对象,接下来,将介绍如何在页面中调用客户端中的函数和对象:

(1) 通过window.external调用

下面将示例如何通过window.external调用客户端中的函数,假设在C++中定义了一个名为CppCall的函数:

 
  1. void CppCall()

  2. {

  3. MessageBox(NULL, L"您调用了CppCall", L"提示(C++)", 0);

  4. }

定义一个对象,并且实现IDispatch接口:

 
  1. class ClientCall:public IDispatch

  2. {

  3. long _refNum;

  4. public:

  5. ClientCall()

  6. {

  7. _refNum = 1;

  8. }

  9. ~ClientCall(void)

  10. {

  11. }

  12. public:

  13. // IUnknown Methods

  14. STDMETHODIMP QueryInterface(REFIID iid,void**ppvObject)

  15. {

  16. *ppvObject = NULL;

  17. if (iid == IID_IUnknown) *ppvObject = this;

  18. else if (iid == IID_IDispatch) *ppvObject = (IDispatch*)this;

  19. if(*ppvObject)

  20. {

  21. AddRef();

  22. return S_OK;

  23. }

  24. return E_NOINTERFACE;

  25. }

  26. STDMETHODIMP_(ULONG) AddRef()

  27. {

  28. return ::InterlockedIncrement(&_refNum);

  29. }

  30. STDMETHODIMP_(ULONG) Release()

  31. {

  32. ::InterlockedDecrement(&_refNum);

  33. if(_refNum == 0)

  34. {

  35. delete this;

  36. }

  37. return _refNum;

  38. }

  39. // IDispatch Methods

  40. HRESULT _stdcall GetTypeInfoCount(

  41. unsigned int * pctinfo)

  42. {

  43. return E_NOTIMPL;

  44. }

  45. HRESULT _stdcall GetTypeInfo(

  46. unsigned int iTInfo,

  47. LCID lcid,

  48. ITypeInfo FAR* FAR* ppTInfo)

  49. {

  50. return E_NOTIMPL;

  51. }

  52. HRESULT _stdcall GetIDsOfNames(

  53. REFIID riid,

  54. OLECHAR FAR* FAR* rgszNames,

  55. unsigned int cNames,

  56. LCID lcid,

  57. DISPID FAR* rgDispId

  58. )

  59. {

  60. if(lstrcmp(rgszNames[0], L"CppCall")==0)

  61. {

  62. //网页调用window.external.CppCall时,会调用这个方法获取CppCall的ID

  63. *rgDispId = 100;

  64. }

  65. return S_OK;

  66. }

  67. HRESULT _stdcall Invoke(

  68. DISPID dispIdMember,

  69. REFIID riid,

  70. LCID lcid,

  71. WORD wFlags,

  72. DISPPARAMS* pDispParams,

  73. VARIANT* pVarResult,

  74. EXCEPINFO* pExcepInfo,

  75. unsigned int* puArgErr

  76. )

  77. {

  78. if(dispIdMember == 100)

  79. {

  80. //网页调用CppCall时,或根据获取到的ID调用Invoke方法

  81. CppCall();

  82. }

  83. return S_OK;

  84. }

  85. };

定义类ClientCall后,就可以创建一个ClientCall的对象,传递给WebBrowser,使得网页中可以通过window.external调用CppCall,要实现这些功能,WebBrowser需要实现IDocHostUIHandler接口,并重写GetExternal方法以返回一个ClientCall对象:

 
  1. ClientCall *pClientCall;

  2. pClientCall = new ClientCall();

  3. virtual HRESULT STDMETHODCALLTYPE GetExternal(IDispatch **ppDispatch)

  4. {

  5. //重写GetExternal返回一个ClientCall对象

  6. *ppDispatch = pClientCall;

  7. return S_OK;

  8. }

接下来,就可以在网页中调用了:

window.external.CppCall()

(2)向网页传递回调函数

向网页传递回调函数的一个典型应用就是在客户端中用C++处理DOM的事件(例如,处理按钮的onclick事件),这里要注意的是,与C++不同的是,在网页中,所谓的函数,其实就是一个具有call方法的对象,因此,向网页传递一个回调函数,其实就是传递一个实现了call方法的对象,因此,我们必须定义一个C++类,并实现IDispatch接口:

 
  1. typedef void _stdcall JsFunction_Callback(LPVOID pParam);

  2. class JsFunction:public IDispatch

  3. {

  4. long _refNum;

  5. JsFunction_Callback *m_pCallback;

  6. LPVOID m_pParam;

  7. public:

  8. JsFunction(JsFunction_Callback *pCallback, LPVOID pParam)

  9. {

  10. _refNum = 1;

  11. m_pCallback = pCallback;

  12. m_pParam = pParam;

  13. }

  14. ~JsFunction(void)

  15. {

  16. }

  17. public:

  18. // IUnknown Methods

  19. STDMETHODIMP QueryInterface(REFIID iid,void**ppvObject)

  20. {

  21. *ppvObject = NULL;

  22. if (iid == IID_IOleClientSite) *ppvObject = (IOleClientSite*)this;

  23. else if (iid == IID_IUnknown) *ppvObject = this;

  24. if(*ppvObject)

  25. {

  26. AddRef();

  27. return S_OK;

  28. }

  29. return E_NOINTERFACE;

  30. }

  31. STDMETHODIMP_(ULONG) AddRef()

  32. {

  33. return ::InterlockedIncrement(&_refNum);

  34. }

  35. STDMETHODIMP_(ULONG) Release()

  36. {

  37. ::InterlockedDecrement(&_refNum);

  38. if(_refNum == 0)

  39. {

  40. delete this;

  41. }

  42. return _refNum;

  43. }

  44. // IDispatch Methods

  45. HRESULT _stdcall GetTypeInfoCount(

  46. unsigned int * pctinfo)

  47. {

  48. return E_NOTIMPL;

  49. }

  50. HRESULT _stdcall GetTypeInfo(

  51. unsigned int iTInfo,

  52. LCID lcid,

  53. ITypeInfo FAR* FAR* ppTInfo)

  54. {

  55. return E_NOTIMPL;

  56. }

  57. HRESULT _stdcall GetIDsOfNames(

  58. REFIID riid,

  59. OLECHAR FAR* FAR* rgszNames,

  60. unsigned int cNames,

  61. LCID lcid,

  62. DISPID FAR* rgDispId

  63. )

  64. {

  65. //令人费解的是,网页调用函数的call方法时,没有调用GetIDsOfNames获取call的ID,而是直接调用Invoke

  66. return E_NOTIMPL;

  67. }

  68. HRESULT _stdcall Invoke(

  69. DISPID dispIdMember,

  70. REFIID riid,

  71. LCID lcid,

  72. WORD wFlags,

  73. DISPPARAMS* pDispParams,

  74. VARIANT* pVarResult,

  75. EXCEPINFO* pExcepInfo,

  76. unsigned int* puArgErr

  77. )

  78. {

  79. m_pCallback(m_pParam);

  80. return S_OK;

  81. }

  82. };

接下来,我们就可以使用JsFunction向网页传递回调,以下代码用于处理按钮的onclick事件:

 
  1. static void _stdcall button1_onclick(LPVOID pParam)

  2. {

  3. MainForm *pMainForm = (MainForm*)pParam;

  4. MessageBox(pMainForm->hWnd, L"您点击了button1", L"提示(C++)", 0);

  5. }

  6. VARIANT params[10];

  7. //获取window

  8. IDispatch *pHtmlWindow = GetHtmlWindow();

  9. //获取document

  10. CVariant document;

  11. params[0].vt = VT_BSTR;

  12. params[0].bstrVal = L"document";

  13. GetProperty(pHtmlWindow, L"document", &document);

  14. //获取button1

  15. CVariant button1;

  16. params[0].vt = VT_BSTR;

  17. params[0].bstrVal = L"button1";

  18. InvokeMethod(document.pdispVal, L"getElementById", &button1, params, 1);

  19. //处理button1的onclick事件

  20. params[0].vt = VT_DISPATCH;

  21. params[0].pdispVal = new JsFunction(button1_onclick, this);

  22. SetProperty(button1.pdispVal, L"onclick", params);

以上就是笔者开发Lesktop开源WebIM时使用WebBrowser的经验总结,如有纰漏,敬请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值