如何从 C++ 在 IE 中调用 eval()?

2022-01-14 00:00:00 com internet-explorer c++

随着 IE11 的出现,IHTMLWindow2::execScript() 已弃用.推荐的方法是使用eval() 而不是.我正在通过其 C++ COM 接口使 IE 自动化,但我一直无法找到如何实现这一点.有人可以指出我在搜索中明显错过的例子吗?如果无法通过 eval 执行代码,那么在 execScript 不再可用的情况下,将 JavaScript 代码注入 Internet Explorer 运行实例的适当方法是什么?

With the advent of IE11, IHTMLWindow2::execScript() is deprecated. The recommended approach is to use eval() instead. I'm automating IE via its C++ COM interfaces, and I have been unable to find how to accomplish this. Can someone point me to the example I've obviously missed in my searching? If it's not possible to execute code via eval, what's the appropriate way to inject JavaScript code into a running instance of Internet Explorer now that execScript is no longer available?

任何适用于我正在从事的项目的解决方案都必须在进程外工作.我没有使用浏览器帮助对象 (BHO) 或任何类型的 IE 插件.因此,任何涉及无法跨进程正确编组的接口的解决方案都不适合我.

Any solution that will work for the project I'm working on must work out-of-process. I am not using a Browser Helper Object (BHO), or any type of IE plugin. Thus, any solution that involves an interface that cannot be properly marshaled cross-process won't work for me.

推荐答案

我现在已经验证了 eval 方法与 IE9、IE10 和 IE11 一致(为简洁起见跳过了错误检查):

I have now verified the eval approach works consistently with IE9, IE10 and IE11 (error checks skipped for breavity):

CComVariant result;
CComDispatchDriver disp = m_htmlWindow; // of IHTMLWindow2
disp.Invoke1(L"eval", &CComVariant(L"confirm('See this?')"), &result);
result.ChangeType(VT_BSTR);
MessageBoxW(V_BSTR(&result));

感觉比execScript还要好,因为它实际上返回了result.它也可以在 C# 中使用 WinForms 的 WebBrowser:

Feels even better than execScript, because it actually returns the result. It works also in C# with WinForms' WebBrowser:

var result = webBrowser1.Document.InvokeScript("eval", new object[] { "confirm('see this?')" });
MessageBox.Show(result.ToString());

也就是说,execScript 仍然适用于 IE11 Preview:

That said, execScript still works for IE11 Preview:

CComVariant result;
m_htmlWindow->execScript(CComBSTR(L"confirm('See this too?')"), CComBSTR(L"JavaScript"), &result);
result.ChangeType(VT_BSTR);
MessageBoxW(V_BSTR(&result));

它仍然像往常一样丢弃 result.

And it still discards the result, as it always did.

有点离题,但您不必为此坚持使用 eval.这种方法允许在加载页面的 JavaScript window 对象的命名空间内执行任何可用的命名方法(通过 IDispatch 接口).你可以调用你自己的函数并将一个活动的 COM 对象传递给它,而不是一个字符串参数,例如:

A bit off-topic, but you don't have to stick with eval for this. This approach allows to execute any named method available inside the namespace of the JavaScript window object of the loaded page (via IDispatch interface). You may call your own function and pass a live COM object into it, rather than a string parameter, e.g.:

// JavaScript
function AlertUser(user)
{
  alert(user.name);
  return user.age;
}

// C++
CComDispatchDriver disp = m_htmlWindow; // of IHTMLWindow2
disp.Invoke1(L"AlertUser", &CComVariant(userObject), &result);

如果可能的话,我更喜欢上述对 eval 的直接调用.

I'd prefer the above direct call to eval where possible.

[已编辑]

需要进行一些调整才能使这种方法适用于进程外调用.正如@JimEvans 在评论中指出的那样,Invoke 返回错误 0x80020006(未知名称").但是,测试 HTA 应用程序 工作得很好,是什么让我想到尝试 IDispatchEx::GetDispId 用于名称解析.这确实有效(跳过了错误检查):

It takes some tweaks to make this approach work for out-of-process calls. As @JimEvans pointed out in the comments, Invoke was returning error 0x80020006 ("Unknown name"). However, a test HTA app worked just fine, what made me think to try IDispatchEx::GetDispId for name resolution. That indeed worked (error checks skipped):

CComDispatchDriver dispWindow;
htmlWindow->QueryInterface(&dispWindow);

CComPtr<IDispatchEx> dispexWindow;
htmlWindow->QueryInterface(&dispexWindow);

DISPID dispidEval = -1;
dispexWindow->GetDispID(CComBSTR("eval"), fdexNameCaseSensitive, &dispidEval);
dispWindow.Invoke1(dispidEval, &CComVariant("function DoAlert(text) { alert(text); }")); // inject

DISPID dispidDoAlert = -1;
dispexWindow->GetDispID(CComBSTR("DoAlert"), fdexNameCaseSensitive, &dispidDoAlert) );
dispWindow.Invoke1(dispidDoAlert, &CComVariant("Hello, World!")); // call

完整的 C++ 测试应用在这里:http://pastebin.com/ccZr0cG2

The full C++ test app is here: http://pastebin.com/ccZr0cG2

[更新]

此更新在进程外的子 iframewindow 对象上创建 __execScript 方法.要注入的代码经过优化,可以返回目标 window 对象以供以后使用(无需进行一系列进程外调用来获取 iframe 对象,它是在主窗口的上下文中完成的):

This update creates __execScript method on a window object of a child iframe, out-of-proc. The code to be injected was optimized to return the target window object for later use (no need to make a series of out-of-proc calls to obtain the iframe object, it's done in the context of the main window):

CComBSTR __execScriptCode(L"(window.__execScript = function(exp) { return eval(exp); }, window.self)");

以下是 C++ 控制台应用程序 (pastebin) 的代码,为简洁起见跳过了一些错误检查..HTA中也有对应的prototype,可读性更强.

Below is the code for C++ console app (pastebin), some error checks skipped for breavity. There's also a corresponding prototype in .HTA, which is more readable.

//
// http://stackoverflow.com/questions/18342200/how-do-i-call-eval-in-ie-from-c/18349546//
//

#include <tchar.h>
#include <ExDisp.h>
#include <mshtml.h>
#include <dispex.h>
#include <atlbase.h>
#include <atlcomcli.h>

#define _S(a) 
    { HRESULT hr = (a); if (FAILED(hr)) return hr; } 

#define disp_cast(disp) 
    ((CComDispatchDriver&)(void(static_cast<IDispatch*>(disp)), reinterpret_cast<CComDispatchDriver&>(disp)))

struct ComInit {
    ComInit() { ::CoInitialize(NULL); }
    ~ComInit() { CoUninitialize(); }
};

int _tmain(int argc, _TCHAR* argv[])
{
    ComInit comInit;

    CComPtr<IWebBrowser2> ie;
    _S( ie.CoCreateInstance(L"InternetExplorer.Application", NULL, CLSCTX_LOCAL_SERVER) );
    _S( ie->put_Visible(VARIANT_TRUE) );
    CComVariant ve;
    _S( ie->Navigate2(&CComVariant(L"http://jsfiddle.net/"), &ve, &ve, &ve, &ve) );

    // wait for page to finish loading
    for (;;)
    {
        Sleep(250);
        READYSTATE rs = READYSTATE_UNINITIALIZED;
        ie->get_ReadyState(&rs);
        if ( rs == READYSTATE_COMPLETE )
            break;
    }

    // inject __execScript into the main window

    CComPtr<IDispatch> dispDoc;
    _S( ie->get_Document(&dispDoc) );
    CComPtr<IHTMLDocument2> htmlDoc;
    _S( dispDoc->QueryInterface(&htmlDoc) );
    CComPtr<IHTMLWindow2> htmlWindow;
    _S( htmlDoc->get_parentWindow(&htmlWindow) );
    CComPtr<IDispatchEx> dispexWindow;
    _S( htmlWindow->QueryInterface(&dispexWindow) );

    CComBSTR __execScript("__execScript");
    CComBSTR __execScriptCode(L"(window.__execScript = function(exp) { return eval(exp); }, window.self)");

    DISPID dispid = -1;
    _S( dispexWindow->GetDispID(CComBSTR("eval"), fdexNameCaseSensitive, &dispid) );
    _S( disp_cast(dispexWindow).Invoke1(dispid, &CComVariant(__execScriptCode)) ); 

    // inject __execScript into the child frame

    WCHAR szCode[1024];
    wsprintfW(szCode, L"document.all.tags("iframe")[0].contentWindow.eval("%ls")", __execScriptCode.m_str);

    dispid = -1;
    _S( dispexWindow->GetDispID(__execScript, fdexNameCaseSensitive, &dispid) );
    CComVariant vIframe;
    _S( disp_cast(dispexWindow).Invoke1(dispid, &CComVariant(szCode), &vIframe) ); // inject __execScript and return the iframe's window object
    _S( vIframe.ChangeType(VT_DISPATCH) );

    CComPtr<IDispatchEx> dispexIframe;
    _S( V_DISPATCH(&vIframe)->QueryInterface(&dispexIframe) );

    dispid = -1;
    _S( dispexIframe->GetDispID(__execScript, fdexNameCaseSensitive, &dispid) );
    _S( disp_cast(dispexIframe).Invoke1(dispid, &CComVariant("alert(document.URL)")) ); // call the code inside child iframe

    return 0;
}

相关文章