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

12
我在我的C++应用程序中嵌入了一个Web浏览器控件。我希望在Web浏览器控件中运行的JavaScript能够调用C++函数/方法。
我发现有三种方法可以实现这一点:
1. 实现一个作为中间人的ActiveX组件。(实现细节在此处:http://blogs.msdn.com/b/nicd/archive/2007/04/18/calling-into-your-bho-from-a-client-script.aspx
2. 使用window.external。(也在上面的链接中讨论,但未提供实现)
3. 向window对象添加自定义对象。
我想选择第三个选项,但我没有找到任何可行的示例。请问是否有人可以向我展示如何做,或者在网络上链接到一个可行的示例。
我找到的最接近示例是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,并将*this作为* 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:

移至一个单独的问题

编辑6:

将“返回字符串”编辑制作成一个单独的问题。这样我就可以接受Georg的回答,因为它回答了原始问题。

编辑7:

我收到了一些关于完全可行的自包含示例实现的请求。在这里:https://github.com/Tobbe/CppIEEmbed。如果您能,请分叉并改进 :)


我现在遇到了与你类似的问题...考虑到你已经成功完成了这个,你是否考虑在某个地方提供一个可下载的工作示例? - titel
你不是第一个需要一个简单的自包含示例的人。我会在有时间时制作一个(可能要几个月后)。与此同时,您可以查看此链接,因为这是我使用此代码的地方:https://github.com/tobbe/lsactivedesktop - Tobbe
1
@标题:这是一个小的自包含示例:https://github.com/Tobbe/CppIEEmbed - Tobbe
只需更改URL即可。它不必是lionwiki,可以是任何东西 :) - Tobbe
不,CppIEEmbed仅展示了如何嵌入网页。如果你想看如何调用C++函数,你需要查看https://github.com/Tobbe/lsactivedesktop中的代码。 - Tobbe
显示剩余5条评论
1个回答

5
您需要实现GetIDsOfNames()以执行一些有意义的操作,因为客户端代码将在调用Invoke()之前调用该函数。
如果您的接口在类型库中,请参见此处的示例。如果您想使用晚期绑定,可以使用大于DISPID_VALUE且小于0x80010000DISPID(所有值<= 0且在0x800100000x8001FFFF范围内的值均已保留):
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 ...
    }

    // ...

请注意,DISPID不应该突然更改,因此应使用静态map或常量值。

那个例子以及我找到的其他例子都使用了DispGetIDsOfNames函数,该函数需要一个“ITypeInfo”指针。这个ITypeInfo是什么东西,我怎样才能创建/获取一个可以在我的情况下使用的呢? - Tobbe
我在GetIDsOfNames中返回的ID(DISPID),它们只用于Invoke()中吗?还是它们在更多的地方使用。我真正想知道的是,我是否可以自己编造它们,或者我必须使用类似GetDispID这样的方法来为我生成它们。(使用编造/随机ID似乎可以工作,但我只是想知道这样做是否是一个坏主意) - Tobbe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接