Ответ 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;
}