从运行在 Web 浏览器控件中的 JavaScript 脚本调用 C++ 函数

Posted

技术标签:

【中文标题】从运行在 Web 浏览器控件中的 JavaScript 脚本调用 C++ 函数【英文标题】:Calling C++ function from JavaScript script running in a web browser control 【发布时间】:2011-04-14 10:16:59 【问题描述】:

我在我的 c++ 应用程序中嵌入了一个 Web 浏览器控件。我希望在 Web 浏览器控件中运行的 javascript 能够调用 c++ 函数/方法。

我发现了三种方法可以做到这一点:

    实现一个充当中间人的 ActiveX 组件。 (这里的实现细节:http://blogs.msdn.com/b/nicd/archive/2007/04/18/calling-into-your-bho-from-a-client-script.aspx) 使用 window.external。 (也在上面的链接中讨论过,但没有提供实现) 向窗口对象添加自定义对象

我想选择第三个选项,但我还没有找到任何可行的例子来说明如何做到这一点。有人可以告诉我怎么做,或者链接到网上某个地方的工作示例。

我发现的最接近示例的是 Igor Tandetnik 在a thread in the webbrowser_ctl news group 中的第一个回复。但恐怕我需要更多的帮助。

我正在嵌入一个 IWebBrowser2 控件,但我没有使用 MFC、ATL 或 WTL。

编辑:

按照我之前链接的线程中 Igor 给出的伪代码,以及在 codeproject 文章“Creating JavaScript arrays and other objects from C++”中找到的代码,我已经生成了一些代码。

void WebForm::AddCustomObject(IDispatch *custObj, std::string name)

    IhtmlDocument2 *doc = GetDoc();
    IHTMLWindow2 *win = NULL;
    doc->get_parentWindow(&win);

    if (win == NULL) 
        return;
    

    IDispatchEx *winEx;
    win->QueryInterface(&winEx);

    if (winEx == NULL) 
        return;
    

    int lenW = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, name.c_str(), -1, NULL, 0);
    BSTR objName = SysAllocStringLen(0, lenW);
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, name.c_str(), -1, objName, lenW);

    DISPID dispid; 
    HRESULT hr = winEx->GetDispID(objName, fdexNameEnsure, &dispid);

    SysFreeString(objName);

    if (FAILED(hr)) 
        return;
    

    DISPID namedArgs[] = DISPID_PROPERTYPUT;
    DISPPARAMS params;
    params.rgvarg = new VARIANT[1];
    params.rgvarg[0].pdispVal = custObj;
    params.rgvarg[0].vt = VT_DISPATCH;
    params.rgdispidNamedArgs = namedArgs;
    params.cArgs = 1;
    params.cNamedArgs = 1;

    hr = winEx->InvokeEx(dispid, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL); 

    if (FAILED(hr)) 
        return;
    

上面的代码一直运行,所以到目前为止一切看起来都很好。

当我收到 DISPID_NAVIGATECOMPLETE2 DWebBrowserEvents2 事件时,我调用 AddCustomObject,并将其传递为 *custObj

class JSObject : public IDispatch 
private:
    long ref;

public:
    // IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    // IDispatch
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo);
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid,
        ITypeInfo **ppTInfo);
    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid,
        LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid,
        LCID lcid, WORD wFlags, DISPPARAMS *Params, VARIANT *pVarResult,
        EXCEPINFO *pExcepInfo, UINT *puArgErr);
;

值得注意的实现可能是

HRESULT STDMETHODCALLTYPE JSObject::QueryInterface(REFIID riid, void **ppv)

    *ppv = NULL;

    if (riid == IID_IUnknown || riid == IID_IDispatch) 
        *ppv = static_cast<IDispatch*>(this);
    

    if (*ppv != NULL) 
        AddRef();
        return S_OK;
    

    return E_NOINTERFACE;

HRESULT STDMETHODCALLTYPE JSObject::Invoke(DISPID dispIdMember, REFIID riid,
    LCID lcid, WORD wFlags, DISPPARAMS *Params, VARIANT *pVarResult,
    EXCEPINFO *pExcepInfo, UINT *puArgErr)

    MessageBox(NULL, "Invoke", "JSObject", MB_OK);
    return DISP_E_MEMBERNOTFOUND;

不幸的是,当我尝试使用 javascript 代码中的“JSObject”对象时,我从未收到“Invoke”消息框。

JSObject.randomFunctionName();  // This should give me the c++ "Invoke" message
                                // box, but it doesn't

编辑 2:

我像这样实现了GetIDsOfNames

HRESULT STDMETHODCALLTYPE JSObject::GetIDsOfNames(REFIID riid,
    LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)

    HRESULT hr = S_OK;

    for (UINT i = 0; i < cNames; i++) 
        std::map<std::wstring, DISPID>::iterator iter = idMap.find(rgszNames[i]);
        if (iter != idMap.end()) 
            rgDispId[i] = iter->second;
         else 
            rgDispId[i] = DISPID_UNKNOWN;
            hr = DISP_E_UNKNOWNNAME;
        
    

    return hr;

这是我的构造函数

JSObject::JSObject() : ref(0)

    idMap.insert(std::make_pair(L"execute", DISPID_USER_EXECUTE));
    idMap.insert(std::make_pair(L"writefile", DISPID_USER_WRITEFILE));
    idMap.insert(std::make_pair(L"readfile", DISPID_USER_READFILE));

将 DISPID_USER_* 常量定义为私有类成员

class JSObject : public IDispatch 
private:
    static const DISPID DISPID_USER_EXECUTE = DISPID_VALUE + 1;
    static const DISPID DISPID_USER_WRITEFILE = DISPID_VALUE + 2;
    static const DISPID DISPID_USER_READFILE = DISPID_VALUE + 3;

    // ...
;

编辑 3、4 和 5:

移至a separate question

编辑 6:

从“返回字符串”编辑中制作了a separate question。这样我就可以接受Georg's 回复,因为它回答了原始问题。

编辑 7:

我收到了一些关于完全工作、自包含的示例实现的请求。这是:https://github.com/Tobbe/CppIEEmbed。如果可以的话,请分叉和改进:)

【问题讨论】:

GetIDsOfNames() 会返回一些有意义的东西吗? @Georg:不,它没有。它只是返回 E_FAIL。 我现在遇到了和你类似的问题......考虑到你已经设法做到了这一点,你会考虑在某处提供一个可以下载的工作示例吗? 您不是第一个想要一个小型独立示例的人。有时间的时候我会做一个(可能几个月后)。同时你可以看看这个,因为那是我使用这个代码的地方:github.com/tobbe/lsactivedesktop @titel:这是一个独立的小例子:github.com/Tobbe/CppIEEmbed 【参考方案1】:

您需要实现GetIDsOfNames() 来做一些明智的事情,因为客户端代码将在Invoke() 之前调用该函数。 如果您的接口在类型库中,请参阅 here 以获取示例。如果你想改用后期绑定,你可以使用大于DISPID_VALUE和小于0x80010000DISPIDs(所有值&lt;= 00x800100000x8001FFFF范围内的值都是保留的):

HRESULT GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, 
                      LCID lcid, DISPID *rgDispId)

    HR hr = S_OK;
    for (UINT i=0; i<cNames; ++i) 
        if (validName(rgszNames)) 
            rgDispId[i] = dispIdForName(rgszNames);
         else 
            rgDispId[i] = DISPID_UNKNOWN;
            hr = DISP_E_UNKNOWNNAME;
        
    
    return hr;


HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, 
               DISPPARAMS *Params, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, 
               UINT *puArgErr)

    if (wFlags & DISPATCH_METHOD) 
       // handle according to DISPID ...
    

    // ...

请注意,DISPIDs 不应该突然改变,例如应使用静态 map 或常量值。

【讨论】:

该示例以及我发现的其他示例都使用了需要“ITypeInfo”指针的 DispGetIDsOfNames 函数。这是什么 ITypeInfo 东西,我如何创建/获取一个我可以在我的情况下使用的东西? 我在 GetIDsOfNames 中返回的 ID (DISPID),是否仅在 Invoke() 中使用?或者他们在更多地方使用。我真正想知道的是我是否可以自己制作它们,或者我是否必须使用 GetDispID 之类的东西来为我生成它们。 (使用虚构/随机 ID 似乎可行,但我只想知道这样做是否是个坏主意)

以上是关于从运行在 Web 浏览器控件中的 JavaScript 脚本调用 C++ 函数的主要内容,如果未能解决你的问题,请参考以下文章

MS Access VBA从Web浏览器控件的内容中获取数据

如何从 Web 浏览器控件设置纸张大小和边距打印

从 Web 浏览器控件中读取 Javascript 变量

如何在 MS-Access 的表单上显示 Web 浏览器控件中的表格字段内容?

如何在跨平台应用程序中嵌入 Web 浏览器控件?

Web 浏览器控件中的文档模式菜单功能