WebBrowser介绍——Javascript与C++互操作
WebBrowser控件是Microsoft提供的一个用于网页浏览的客户端控件,WebBrowser控件的使用相当广泛,例如很多邮件客户端都是使用可编辑的WebBrowser控件作为写邮件的工具,也有很多软件用WebBrowser控件弹出网页,如qq的新闻首页。
微软的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()
(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);
WebBrowser介绍——Javascript与C++互操作的更多相关文章
- 转载 WebBrowser介绍——Javascript与C++互操作
		注:本文来自于 http://www.cnblogs.com/lucc/archive/2010/11/24/1886087.html WebBrowser控件是Microsoft提供的一个用于网页浏 ... 
- 一个分门别列介绍JavaScript各种常用工具的脑图
		博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:一个分门别列介绍JavaScript各种常用工具的脑图. 
- 阅读:重新介绍 JavaScript(JS教程)
		这篇文章是记录自己阅读重新介绍 JavaScript(JS 教程)的记录和个人体会 在线调试代码工具:https://codepen.io/pen 引言 分歧根源:名字Javascript和Java有 ... 
- C#中让WebBrowser运行Javascript脚本
		C#中可以让Webbrowser运行Javascript脚本来实现各种自动化操作,比如点击网页上的按钮,输入用户名密码等等.代码也很简单: >>>>>>>&g ... 
- 重新介绍 JavaScript
		简介 为什么需要这个重新介绍呢?因为 JavaScript 已经完全可以被称为世界上被误解最严重的编程语言了.虽然它被当做玩具来用,但是藏在让人迷惑的简单表象下面的,是强大的语言特性.从2005年,一 ... 
- 简单介绍Javascript匿名函数和面向对象编程
		忙里偷闲,简单介绍一下Javascript中匿名函数和闭包函数以及面向对象编程.首先简单介绍一下Javascript中的密名函数. 在Javascript中函数有以下3中定义方式: 1.最常用的定义方 ... 
- C#中webbrowser与javascript(js)交互的方法
		今天在做一个项目的时候需要用c#搞一个webbrowser,然后有些地方还需要与js交互.所以就查了一下资料,发现很多博客提到了但是却没有说下具体的操作.所以我就写一下. 开发环境是Visual St ... 
- winform下利用webBrowser执行javascript
		目前很多网站为了防止恶意提交表单信息,大多都采用了加密的方式对提交信息进行处理,加密处理后通过POST提交给服务器验证,这种操作一般都是用Javascipt进行加密,若是我们想要正确提交表单到网站,就 ... 
- Head First HTML5 Programming笔记--chapter2 介绍Javascript和DOM
		你已经了解了HTML标记(也称为结构),而且知道了CSS样式(也称为表示),剩下的就是Javascript(也称为行为). JavaScript的工作方式 1. 编写 你创建HTML标记和JavaSc ... 
随机推荐
- 1029:Ignatius and the Princess IV
			题目大意是找出数组中出现次数超过一半的数. 基本思想:每遇到两个不同的数就消掉,设一个计数器就行了. 存出现次数最大的那个数的出现次数. 当下一个数与当前的数不同时,计数器减一,相同,则加一. 实 ... 
- localhost能连接websocket 127.0.0.1 不能连接问题?
			最近开发中遇到一个问题,就是有的浏览器电脑能连接websocket , 有的不能 , 有的能用localhost连接,有的能用127.0.0.1连接,这个问题很奇怪 提供一个很好测试websocket ... 
- iframe相关知识
			iframe 不带边框的iframe因为能和网页无缝的结合从而不刷新页面的情况下更新页面的部分数据成为可能,可是 iframe的大小却不像层那样可以“伸缩自如”,所以带来了使用上的麻烦.一般通过百分比 ... 
- 【转】MFC消息处理(一)
			原文网址:http://blog.csdn.net/hyhnoproblem/article/details/6182120 1.MFC窗口如何与AfxWndProc建立联系. 当一个新的CWnd派生 ... 
- centos 7 安装rabbitmq 3.6.12
			0 安装 epel yum -y install http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-7-11. ... 
- 移动端开发之px,em和rem详解
			px:表示的是绝对的像素值,1px就是1像素大小 em:关于em,网上有资料说是关于父元素的,但是其实个人感觉这种说法是不对的,其实em的大小是根据自身的font-size确定的,而只是正常的情况下子 ... 
- Regenerate Script 重置脚本
			1.Regenerate Script 重置回录制后的第一次脚本,当修改设定后点击这个按钮,新的设置也会录制到 如:开始没有录到下载的文件,添加下载文件的个时候,再次点击重置,就录制到了 如:如开始是 ... 
- Java 数组类型转字符串类型
			Java手册 String public String() 初始化一个新创建的 String 对象,使其表示一个空字符序列.注意,由于 String 是不可变的,所以无需使用此构造方法. String ... 
- 面试总结之C/C++
			source code https://github.com/haotang923/interview/blob/master/interview%20summary%20of%20C%20and%2 ... 
- Python链表与反链表
			# -*- coding:utf8 -*- #/usr/bin/env python class Node(object): def __init__(self, data, pnext = None ... 
