【问题标题】:How do I call eval() in IE from C++?如何从 C++ 在 IE 中调用 eval()?
【发布时间】:2013-08-22 22:13:48
【问题描述】:

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

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

【问题讨论】:

  • 我猜你会像在 JavaScript 中那样做,通过向页面的 DOM 添加一个新的脚本元素,但我正在与 IE 开发团队核实......
  • @JimEvans,我没有安装 IE11 来尝试,但以下适用于使用 eval 的 IE10:CComDispatchDriver window = m_window; /* of IHTMLWindow2 */ window.Invoke1(L"eval", &CComVariant(L"alert(true)"));

标签: c++ internet-explorer com


【解决方案1】:

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

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:

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

也就是说,execScript 仍然适用于 IE11 预览版:

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

它仍然像往常一样丢弃result

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

// 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

[已编辑]

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

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

[更新]

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

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

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

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

【讨论】:

  • 就目前而言,您的解决方案还不错,但不能完全满足我的需求。尝试在框架/iframe 的上下文中执行脚本将不起作用。 IDispatchEx::GetDispID 为与框架关联的 IHTMLWindow2 对象返回相同的“未知名称”HRESULT。
  • 您能否详细说明如何在进程外获取框架的窗口 IHTMLWindow2 对象?我已经尝试过browser.Document.all.tags("iframe")[0].contentWindow,但我在最后一步得到 Permission denied - contentWindow (IE10)。
  • 这应该可以工作,并且在我的环境中也可以,只要您在此过程中不跨越任何保护模式边界。我的项目的完整代码可在on GitHub 获得,特别是在其中的 IEDriverServer 项目中。 DocumentHost 类包含用于更改框架焦点的代码。
  • @wilx 我有点晚了,但你可以继续使用 execScript:stackoverflow.com/a/31605264/1160796
  • @basher,是的,他们现在拥有WebDriver for Edge,他们承诺将与 Edge 一起不断更新。但是还有另一个新兴趋势:ElectronJS。这允许放弃 IE 遗留并使用桌面信封包装整个 Web 应用程序,因此它不仅可以在 Win7 上运行,还可以在 Mac 和 Linux 上运行,使用尖端的 Web 标准(包括您喜欢的 async/await)。 I'm jumping on that wagon, too ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-11
  • 2010-12-17
  • 2013-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多