• 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);
    

      

  • 相关阅读:
    男子胃脘胀痛案(知老)
    转 Django中的Form
    转 Alert.log shows No Standby Redo Logfiles Of Size 153600 Blocks Available
    转发 django 初探
    转 Logs are not shipped to the physical standby database
    python 之django (一) Windows环境下Django 1.6.11开发环境搭建(简易版)
    oracle db 产品路线图
    转 python中%s与%d
    转 python 的常用函数replace, split(),enumerate() 函数
    转 [Error]EOL while scanning string literal
  • 原文地址:https://www.cnblogs.com/guohu/p/5481046.html
Copyright © 2020-2023  润新知