注:本文来自于 http://www.cnblogs.com/lucc/archive/2010/11/24/1886087.html

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++互操作的更多相关文章

  1. 一个分门别列介绍JavaScript各种常用工具的脑图

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:一个分门别列介绍JavaScript各种常用工具的脑图.

  2. 重新介绍 JavaScript

    简介 为什么需要这个重新介绍呢?因为 JavaScript 已经完全可以被称为世界上被误解最严重的编程语言了.虽然它被当做玩具来用,但是藏在让人迷惑的简单表象下面的,是强大的语言特性.从2005年,一 ...

  3. 简单介绍Javascript匿名函数和面向对象编程

    忙里偷闲,简单介绍一下Javascript中匿名函数和闭包函数以及面向对象编程.首先简单介绍一下Javascript中的密名函数. 在Javascript中函数有以下3中定义方式: 1.最常用的定义方 ...

  4. [转载]WebBrowser控件表单(form)的自动填写和提交

    话说有了WebBrowser类,终于不用自己手动封装SHDocVw的AxWebBrowser这个ActiveX控件了.这个类如果仅仅作为一个和IE一模一样浏览器,那就太没意思了(还不如直接用IE呢). ...

  5. 转载 VPN介绍

    转载原地址:  http://aajs800.blog.51cto.com/519255/239724  原作者 aajs800 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者 ...

  6. 转载 深入理解JavaScript中的this关键字

    转载原地址: http://www.cnblogs.com/rainman/archive/2009/05/03/1448392.html 深入理解JavaScript中的this关键字   1. 一 ...

  7. (转载)使用JavaScript操作表单

    (转载)http://www.blogjava.net/junglesong/archive/2008/03/02/183263.html 使用JavaScript操作表单 获取表单的引用 在开始对表 ...

  8. 【转载】介绍“Razor”— ASP.NET的一个新视图引擎

    最近在做一个项目,用的MVC razor 视图,因为之前没用这个视图做过,于是查阅文档资料,共享一下. https://msdn.microsoft.com/zh-cn/ff849693 内容主要是讲 ...

  9. 两个示例介绍JavaScript的闭包

    JavaScript的闭包有两个用途:一个是访问函数内部的变量:另一个是让变量的值在作用域内保持不变.函数是JavaScript 中唯一有作用域的对象,因此JavaScript的闭包依赖于函数实现,下 ...

随机推荐

  1. Power BI官方视频(3) Power BI Desktop 8月份更新功能概述

    Power BI Desktop 8月24日发布了更新版本.现将更新内容翻译整理如下,可以根据后面提供的链接下载最新版本使用. 1.主要功能更新 1.1 数据钻取支持在线版 以前的desktop中进行 ...

  2. GridView详细介绍

    GridView控件的属性 表10.6 GridView控件的行为属性属性描述AllowPaging指示该控件是否支持分页.AllowSorting指示该控件是否支持排序.AutoGenerateCo ...

  3. IOS开发遇到(null)与&lt;null&gt;轻松处理

    在ios开发中不可避免的我们会遇到服务器返回的值有空值,但是如果是nil也就算了还可能得到(null)以及<null>的返回值,该如何处理呢?(当然有的字典转模型中已处理,可以通过遍历等) ...

  4. CorelDRAW x6 X8安装失败解决方法

    CorelDRAW x6 X8自定义安装时,到最后经常会出现以下问题: 解决方法如下: 在自定义安装时,出现以下这个界面时,点击红色箭头的地方 将下图红色箭头指向的选项,点击取消,不要选上,即可解决安 ...

  5. WinForm 窗体属性 窗体美化

    WinForm是·Net开发平台中对Windows Form的一种称谓. Windows窗体的一些重要特点如下: 功能强大:Windows窗体可用于设计窗体和可视控件,以创建丰富的基于Windows的 ...

  6. servlet filter和springMVC拦截器的区别

    参考 http://blog.csdn.net/ggibenben1314/article/details/45341855

  7. ZT 螨虫知识2

    病情分析:过敏是治不好的,只能做到避免接触.指导意见:螨虫的话就不要跟狗多接触,狗的寄生虫很多,还有草地,尤其是狗经常去的地方,草地就是螨虫的传播介质.你是过敏性体质除了被免过敏性源外,还要增强体质, ...

  8. 【Alpha版本】冲刺阶段——Day 5

    我说的都队 031402304 陈燊 031402342 许玲玲 031402337 胡心颖 03140241 王婷婷 031402203 陈齐民 031402209 黄伟炜 031402233 郑扬 ...

  9. BROOTKIT Pinciple、Code Analysis(undone)

    目录 . Rootkit相关知识 . BROOTKIT源码分析 . 关键技术点 . 防御策略 1. Rootkit相关知识 关于rootkit的相关其他知识,请参阅以下文章 http://www.cn ...

  10. Android 分享一个SharedPreferences的工具类,方便保存数据

    我们平常保存一些数据,都会用到SharedPreferences,他是保存在手机里面的,具体路径是data/data/你的包名/shared_prefs/保存的文件名.xml, SharedPrefe ...