2013-08-20 17 views
12

Wraz z nadejściem IE11, IHTMLWindow2::execScript() jest przestarzałe. Zalecane podejście to use eval() instead. Zautomatyzowałem IE poprzez jego interfejsy C++ COM i nie byłem w stanie znaleźć sposobu, aby to osiągnąć. Czy ktoś może wskazać mi przykład, którego oczywiście nie udało mi się przeszukać? Jeśli nie można wykonać kodu przez eval, jaki jest odpowiedni sposób na wstrzyknięcie kodu JavaScript do działającej instancji Internet Explorera, ponieważ teraz execScript nie jest już dostępny?Jak wywołać eval() w IE z C++?

EDYCJA: Każde rozwiązanie, które będzie działać w projekcie, nad którym pracuję, musi działać poza procesem. Nie używam Browser Helper Object (BHO) ani żadnego innego typu wtyczki IE. W związku z tym każde rozwiązanie, które wymaga interfejsu, który nie może być prawidłowo skonstruowany w trybie krzyżowym, nie będzie działać dla mnie.

+1

Domyślam się, że chcesz to zrobić jak chcesz w JavaScript, dodając nowy element skryptu do DOM strony, ale mam sprawdzanie z zespołem IE dev ... – EricLaw

+1

@JimEvans , Nie mam zainstalowanego IE11, aby spróbować, ale następujące prace dla IE10 przy użyciu 'eval':' CComDispatchDriver window = m_window;/* z IHTMLWindow2 */ window.Invoke1 (L "eval", i CComVariant (L "alert (true)")); ' – Noseratio

Odpowiedz

12

Mam teraz zweryfikowane podejście eval działa konsekwentnie z IE9, IE10 i IE11 (sprawdza błędów pomijane dla 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)); 

Temp nawet lepiej niż execScript, ponieważ faktycznie zwraca result. To działa również w języku C# z WinForms' WebBrowser:

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

Powiedział, że execScript nadal pracuje dla 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)); 

i nadal odrzuca result, jak zawsze.

Trochę nie na temat, ale nie musisz trzymać się tego za pomocą eval. Takie podejście pozwala na wykonanie dowolnej nazwanej metody dostępnej w przestrzeni nazw obiektu JavaScript window załadowanej strony (za pośrednictwem interfejsu IDispatch). Możecie nazywać swoją funkcję i przechodzą na żywo obiektu COM do niego, zamiast parametru ciąg, np .:

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

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

bym w miarę możliwości wolą powyższy bezpośredniego połączenia do eval.

[edited]

To trwa kilka poprawek do tej pracy podejście do out-of-process połączeń. Jak zauważyli @JimEvans w komentarzach, Invoke zwrócił błąd 0x80020006 ("Nieznana nazwa"). Jednak test HTA app działało dobrze, co sprawiło, że pomyślałem, aby spróbować IDispatchEx::GetDispId dla rozpoznawania nazw. Że rzeczywiście przepracowanych (sprawdza błędów pominięta):

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 

Pełne C++ aplikacja test jest tutaj: http://pastebin.com/ccZr0cG2

[UPDATE]

Ta aktualizacja tworzy __execScript sposób na window obiektu dziecka iframe, poza procesem.Kod, który ma zostać wstrzyknięty, został zoptymalizowany w celu zwrócenia obiektu docelowego w celu późniejszego użycia (nie ma potrzeby wykonywania serii wywołań poza proces w celu uzyskania obiektu iframe, odbywa się to w kontekście głównego okna):

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

Poniżej znajduje się kod aplikacji konsoli C++ (pastebin), niektóre pomyłki zostały pominięte w celu wykluczenia. Istnieje również odpowiednia prototype in .HTA, która jest bardziej czytelna.

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

Masz całkowitą rację, dzwoniąc do dowolnej nazwanej funkcji. Dokładnie takie podejście ma mój projekt. Jednak wstrzykuje tę funkcję _into_ stronę, na której aktualnie używamy 'execScript', i która musi zostać zastąpiona. N.B., nie mamy żadnej kontroli nad tym, na jakie strony może być używana nasza biblioteka, więc nie możemy zmodyfikować źródła strony, aby bezpośrednio utworzyć funkcję globalną. – JimEvans

+0

W tym przypadku możesz użyć 'eval' najpierw do wstrzyknięcia nowej funkcji globalnej, wywołaj ją. To działa: 'disp.Invoke1 (L" eval ", & CComVariant (funkcja L" GlobalTest() {alert (true);} "));' – Noseratio

+0

Co do twojego problemu poza procesem, nie próbowałem go out-of-proc, ale jestem pewien, że będzie działać tak jak jest. Dokonuje się tego za pomocą 'IDispatch', która automatycznie pobiera marshall. Nie jest wymagana dodatkowa instalacja hydrauliczna. – Noseratio