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

我在我的c ++应用程序中embedded了一个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. 将自定义对象添加到窗口对象

我想用第三种方法,但是我还没有find有关如何做的实例。 有人可以告诉我怎么做,或者链接到网上的一个工作的例子。

最接近我find的例子是Igor Tandetnik在webbrowser_ctl新闻组的一个主题中的第一个回复。 但是恐怕我需要更多的帮助。

我正在embedded一个IWebBrowser2控件,我不使用MFC,ATL或WTL。

编辑:

在我之前链接的线程中,以及在codeproject文章“ 从C ++创buildJavaScript数组和其他对象 ”中find的Igor提供的伪代码,我已经生成了一些代码。

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:

搬到一个单独的问题

编辑6:

从“返回string”编辑中单独提出问题 。 这样我可以接受格奥尔格的答复,因为这回答了原来的问题。

编辑7:

我已经收到了一些完整的,自成体系的示例实现的请求。 这里是: https : //github.com/Tobbe/CppIEEmbed 。 如果可以的话,请叉和改善:)

Solutions Collecting From Web of "从Web浏览器控件中运行JavaScript脚本调用C ++函数"

你需要实现GetIDsOfNames()来做一些明智的事情,因为这个函数将在Invoke()之前被客户端代码Invoke()
如果你有一个类型库中的接口,请参阅这里的例子。 如果要使用后期绑定,则可以使用DISPID更大的DISPID_VALUE且小于0x80010000 (所有值<= 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或常量值。