- 浏览: 4473991 次
- 性别:
- 来自: 杭州
最新评论
-
netkongjian:
不错的软件知识,感谢分享!
软件加密方式 -
norce:
效果不错~
JS实现图片幻灯片效果 -
zxbear:
链接已失效
《jQuery基础教程:第2版》PDF -
架构师:
在技术领域方面Java还是世界上最好的,而且有很多第三方控件的 ...
专访:Ruby能否成为第二个Java -
freddie:
如何拖动表格边框调整行高和列宽?
可编辑的表格(JavaScript)
WebBrowser介绍——Javascript与C++互操作
WebBrowser介绍——Javascript与C++互操作
WebBrowser控件是Microsoft提供的一个用于网页浏览的客户端控件,WebBrowser控件的使用相当广泛,例如很多邮件客户端都是使用可编辑的WebBrowser控件作为写邮件的工具,也有很多软件用WebBrowser控件弹出网页,如qq的个性首页。关于WebBrowser的应用,也可以参考笔者开发的开源WebIM,Lesktop开源WebIM提供的IM客户端就是使用WebBrowser实现的:
微软的MFC和.NET都有WebBrowser控件,这两个控件虽然容易上手,不过由于包装的太好,所以很难深入。因此本文介绍的WebBrowser将不使用MFC和.NET,而是使用C++实现SDK的WebBrowser。
由于本文主要探讨如何实现Javascript与C++的互操作,对于如何使用SDK实现WebBrowser,本文不做详细介绍,读者可以参考以下这篇文章:
http://blog.csdn.net/norsd/archive/2008/09/13/2921389.aspx
不过尽管文章中介绍了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的对象的方法,读取对象的属性,设置对象的属性。
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);
2、在网页中调用客户端的方法
上文我们已经介绍了如何在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()
向网页传递回调函数的一个典型应用就是在客户端中用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; } };
<style type="text/css"> <!-- .csharpcode, .csharpcode pre {font-size:small; color:black; font-family:consolas,"Courier New",courier,monospace} .csharpcode pre {margin:0em} .csharpcode .rem {color:#008000} .csharpcode .kwrd {color:#0000ff} .csharpcode .str {color:#006080} .csharpcode .op {color:#0000c0} .csharpcode .preproc {color:#cc6633} .csharpcode .asp {background-color:#ffff00} .csharpcode .html {color:#800000} .csharpcode .attr {color:#ff0000} .csharpcode .alt {background-color:#f4f4f4; width:100%; margin:0em} .csharpcode .lnum {color:#606060} --> </style>接下来,我们就可以使用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);
以上就是笔者开发Lesktop开源WebIM时使用WebBrowser的经验总结,如有纰漏,敬请指出。
相关推荐
【JavaScript与C++互操作】 在WebBrowser控件中,JavaScript和C++之间的互操作是通过ActiveX技术实现的。ActiveX是一种微软开发的技术,用于创建和使用小型可重用组件,这些组件可以在不同的应用程序之间共享,包括...
这个名为 "JS_CPP.rar" 的压缩包提供了一个实践案例,展示了如何在 JavaScript 和 VC (Visual C++) 之间实现互操作性。这些交互通常通过特定的接口或库来完成,以确保数据和控制流程能在两种语言之间无缝传递。 1. ...
4. **JavaScript与C++互操作**:如果使用WebBrowser控件,可能需要利用ActiveX或JScript接口在C++代码中控制JavaScript的行为。 5. **授权和密钥管理**:使用Google Maps API需要有效的API密钥,开发者需要妥善处理...
"WebBrowser.sln"是一个Visual Studio解决方案文件,这通常意味着项目可能是一个Windows平台上的浏览器插件或扩展,它实现了JavaScript和C++的互操作性。在这样的项目中,开发者可能会使用ActiveX、NPAPI(已过时)...
这个压缩包文件可能包含了一个C#编写的工具,该工具能够执行JavaScript代码,或者是一个C#项目,其中整合了JavaScript引擎以支持JavaScript与C#的互操作。 【描述】"C# js效验小工具 可直接执行js方法" 提示我们这...
4. 实现C++和JavaScript的互操作,让JavaScript可以调用MFC的函数。 5. 在事件处理函数中执行相应的业务逻辑,如打开新的对话框。 这个过程涉及到的技能包括MFC编程、COM组件、HTML DOM操作以及JavaScript编程。...
通过这个控件,用户可以在应用程序内部浏览网页,同时也提供了与网页交互的能力,如执行JavaScript、导航、提交表单等。 “具体解析为C++注册的COM组件”,这里涉及到组件对象模型(Component Object Model, COM)...
5. JavaScript交互:如果需要更复杂的控制,如播放控制、视频选择等,可以利用CefSharp的JavaScript互操作性,通过执行JavaScript代码来操作视频元素。 总的来说,CefSharp v79.1.36的视频播放功能为.NET开发者提供...
2. **C#互操作性**:使用Unity的`UnityPlayer`类和`Application.OpenURL`方法,配合WPF的`System.Windows.Forms.WebBrowser`控件,可以通过URL传递消息。这种方法的限制在于只能单向通信,即从Unity到WPF。 3. **...
它们允许不同系统间进行数据交换,实现跨平台的互操作性。开发者可能使用这些源代码来创建自定义的Web服务,或者集成现有的Web服务到自己的应用中。 3. **浏览器(Browser)**:浏览器是用户访问Web内容的主要工具...