从在Web浏览器控件中运行的JavaScript脚本调用C ++函数 [英] Calling C++ function from JavaScript script running in a web browser control
问题描述
我在我的c ++应用程序中嵌入了一个Web浏览器控件。我想让JavaScript在Web浏览器控件中运行,以便能够调用一个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在 webbrowser_ctl新闻组中的主题。但我恐怕需要更多的帮助。
我嵌入了一个IWebBrowser2控件,我没有使用MFC,ATL或WTL。
编辑:
通过Igor在我之前链接的线程中提供的伪代码,以及代码项目文章从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事件时,调用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 *>
}
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, puArgErr)
{
MessageBox(NULL,Invoke,JSObject,MB_OK);
return DISP_E_MEMBERNOTFOUND;不幸的是我从来没有得到Invoke消息框,当我尝试使用 JSObject.randomFunctionName(); //这应该给我c ++Invoke消息
// box,但它不会
$ b b
编辑2:
我实现了 GetIDsOfNames
p>
HRESULT STDMETHODCALLTYPE JSObject :: GetIDsOfNames(REFIID riid,
LPOLESTR * rgszNames,UINT cNames,LCID lcid,DISPID * rgDispId)
{
HRESULT hr = S_OK;
for(UINT i = 0; i std :: map< std :: wstring,DISPID> :: iterator iter = idMap.find(rgszNames [一世]);
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(Lexecute,DISPID_USER_EXECUTE)) ;
idMap.insert(std :: make_pair(Lwritefile,DISPID_USER_WRITEFILE));
idMap.insert(std :: make_pair(Lreadfile,DISPID_USER_READFILE));
}
将DISPID_USER_ *常数定义为私有类成员
class JSObject:public IDispatch {
private:
static const DISPID DISPID_USER_EXECUTE = DISPID_VALUE +
static const DISPID DISPID_USER_WRITEFILE = DISPID_VALUE + 2;
static const DISPID DISPID_USER_READFILE = DISPID_VALUE + 3;
// ...
};
编辑3,4和5: b
已移至另一个问题
编辑6:
Made 一个单独的问题出返回字符串编辑。这样,我可以接受 Georg's 的回复,因为回答了原始问题。
编辑7:
我收到了一些完全工作,自包含示例实现的请求。这里是: https://github.com/Tobbe/CppIEEmbed 。如果你可以:)
解决方案你需要实现 GetIDsOfNames c $ c>做一些明智的事情,因为该函数将由客户端代码在 Invoke()
之前调用。
如果您的接口类型库,请参见此处的示例。如果您要使用延迟绑定,可以使用 DISPID
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 if(validName(rgszNames)){
rgDispId [i] = dispIdForName(rgszNames);
} else {
rgDispId [i] = DISPID_UNKNOWN;
hr = DISP_E_UNKNOWNNAME;
}
}
return hr;
}
HRESULT调用(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,
DISPPARAMS * Params,VARIANT * pVarResult,EXCEPINFO * pExcepInfo,
UINT * puArgErr)
{
if(wFlags& DISPATCH_METHOD){
//根据DISPID ...处理
}
// ...
请注意, DISPID
突然改变,例如应使用静态映射
或常量值。
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:
- 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)
- Use window.external. (Also discussed in the link above, but no implementation provided)
- 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 :)
解决方案 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 DISPID
s 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 DISPID
s are not supposed to change suddenly, so e.g. a static map
or constant values should be used.
这篇关于从在Web浏览器控件中运行的JavaScript脚本调用C ++函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!