Как я могу вызвать eval() в IE из С++?

С появлением IE11 IHTMLWindow2::execScript() устарел. Рекомендуемый подход - использовать eval() вместо. Я могу автоматизировать IE через его интерфейсы COM С++, и я не смог найти, как это сделать. Может ли кто-нибудь указать мне на пример, который я, очевидно, пропустил в своем поиске? Если невозможно выполнить код через eval, то какой способ вставить код JavaScript в исполняемый экземпляр Internet Explorer теперь, когда execScript больше не доступен?

EDIT: Любое решение, которое будет работать для проекта, над которым я работаю, должно работать вне процесса. Я не использую объект вспомогательного браузера (BHO) или любой тип IE-плагина. Таким образом, любое решение, которое связано с интерфейсом, который не может быть правильно перекрестно обработан, не будет работать для меня.

Ответ 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. Он также работает в С# с WinForms WebBrowser:

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

Тем не менее, execScript все еще работает для 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, как это всегда делалось.

Немного не по теме, но для этого вам не нужно придерживаться 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, где это возможно.

[EDITED]

Требуется несколько настроек, чтобы этот подход работал для вызовов вне процесса. Как отметил @JimEvans в комментариях, Invoke возвращала ошибку 0x80020006 ( "Неизвестное имя" ). Тем не менее, тестовое приложение HTA отлично справилось, что заставило меня подумать попробовать 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

Полное тестовое приложение С++ находится здесь: http://pastebin.com/ccZr0cG2

[ОБНОВЛЕНИЕ]

Это обновление создает метод __execScript для объекта window дочернего iframe, out-of-proc. Код, который нужно вставить, был оптимизирован для возврата целевого объекта window для последующего использования (нет необходимости делать серию внепроцессных вызовов для получения объекта iframe, это делается в контексте главного окна)

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

Ниже приведен код для консольного приложения С++ (pastebin), некоторые проверки ошибок пропущены для нарушения. Также есть соответствующий прототип в .HTA, который более читабельен.

//
// http://stackoverflow.com/info/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 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;
}