How do I call eval() in IE from C++?

c++, com, internet-explorer

Solution

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));

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());

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));

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

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);

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

[EDITED]

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

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

[UPDATE]

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)");

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

Problem

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? EDIT: 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.

Original source

Related problems