呼叫 C++来自在 Web 浏览器控件中运行的 JavaScript 脚本的函数

发布于 2024-09-19 23:54:19 字数 5937 浏览 16 评论 0原文

我在我的 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. 将自定义对象添加到窗口对象

我想使用第三个选项,但我还没有找到任何有关如何执行此操作的工作示例。有人可以告诉我如何做到这一点,或者链接到网络上某处的工作示例。

我发现的最接近的例子是 Igor Tandetnik 在 webbrowser_ctl 新闻组中的线程。但恐怕我需要的帮助还不止这些。

我正在嵌入 IWebBrowser2 控件,并且没有使用 MFC、ATL 或 WTL。

编辑:

按照我之前链接的线程中 Igor 给出的伪代码,以及在 codeproject 文章“从 C++ 创建 JavaScript 数组和其他对象" 我已经生成了一些代码。

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 事件并将其传递为 *custObj 时,我调用 AddCustomObject:

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.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:< /strong>

从“返回字符串”中提出一个单独的问题 ” 编辑。这样我就可以接受Georg的回复,因为它回答了原来的问题。

编辑7:

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

I have embedded a web browser control in my c++ application. I want javascript running in the web browser control to be able to call a c++ function/method.

I have found mentions of three ways to do this:

  1. Implement an ActiveX component that acts as a middle man. (Implementation details here: http://blogs.msdn.com/b/nicd/archive/2007/04/18/calling-into-your-bho-from-a-client-script.aspx)
  2. Use window.external. (Also discussed in the link above, but no implementation provided)
  3. Add a custom object to the window object

I want to go with the third option, but I haven't found any working examples on how to do that. Can someone please show me how to do it, or link to a working example on the net somewhere.

The closest to an example that I have found is the first reply by Igor Tandetnik in a thread in the webbrowser_ctl news group. But I'm afraid I need more help than that.

I'm embedding an IWebBrowser2 control and I am not using MFC, ATL or WTL.

EDIT:

Going by the pseudo-code given by Igor in the thread I linked earlier, and code found in the codeproject article "Creating JavaScript arrays and other objects from C++" I've produced some code.

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, ¶ms, NULL, NULL, NULL); 

    if (FAILED(hr)) {
        return;
    }
}

The code above runs all the way through, so everything looks alright that far.

I call AddCustomObject when I receive the DISPID_NAVIGATECOMPLETE2 DWebBrowserEvents2 event passing this as *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);
};

Noteworthy implementations might be

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;
}

and

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;
}

Unfortunately I never get the "Invoke" message box when I try to use the "JSObject" object from the javascript code.

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

EDIT 2:

I implemented GetIDsOfNames like so:

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;
}

and this is my constructor

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

with the DISPID_USER_* constants defined as private class members

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;

    // ...
};

EDIT 3, 4 and 5:

Moved to a separate question

EDIT 6:

Made a separate question out of the "returning a string" edits. That way I can accept Georg's reply as that answers the original question.

EDIT 7:

I have gotten a few requests for a fully working, self contained, example implementation. Here it is: https://github.com/Tobbe/CppIEEmbed. Please fork and improve if you can :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

后来的我们 2024-09-26 23:54:19

您需要实现 GetIDsOfNames() 来执行一些明智的操作,因为客户端代码将在 Invoke() 之前调用该函数。
如果您的接口位于类型库中,请参阅此处查看示例。如果您想使用后期绑定,可以使用 DISPID< /code>s大于 DISPID_VALUE 且小于 0x80010000(所有值 <= 0 且在 0x80010000 范围内0x8001FFFF 被保留):

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 或常量应使用值。

You need to implement GetIDsOfNames() to do something sensible as that function will be called by client code before Invoke().
If you have your interfaces in a type library see here for an example. If you want to use late-binding instead, you can use DISPIDs greater DISPID_VALUE and less than 0x80010000 (all values <= 0 and in the range 0x80010000 through 0x8001FFFF are reserved):

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 ...
    }

    // ...

Note that the DISPIDs are not supposed to change suddenly, so e.g. a static map or constant values should be used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文