Как автоматизировать веб-приложение IE, которое выдает модальное диалоговое окно HTML?

[Пересмотрен еще раз для ясности]

У меня есть программа на C ++, которая взаимодействует с веб-сайтом. Сайт специфичен для IE, как и моя программа.

Я подключаюсь к запущенному экземпляру IE обычным способом (вне процесса — см код). Как только я получу IWebBrowser2У меня нет проблем с получением IHTMLDocument2 и взаимодействуя с человеком IHTMLElement объекты, заполнение полей и нажатие кнопок.

Но если на веб-странице есть JavaScript, который вызывает window.showModalDialog, Я застрял: мне нужно взаимодействовать с элементами HTML во всплывающем окне, как и с другими страницами; но я не могу получить его IWebBrowser2,

Всплывающее окно всегда называется «Диалог веб-страницы» и представляет собой окно типа Internet Explorer_TridentDlgFrame содержащий Internet Explorer_Server, Но я не могу получить IWebBrowser2 от Internet Explorer_Server окно, как я могу, когда это нормальный экземпляр IE.

Я могу получить IHTMLDocument2Ptr, но когда я пытаюсь получить IWebBrowser2 Я получаю HRESULT из E_NOINTERFACE,

Код довольно стандартный и работает нормально, если это «нормальное» окно IE

IHTMLDocument2Ptr pDoc;
LRESULT lRes;

/* hWndChild is an instance of class "Internet Explorer_Server" */

UINT nMsg = ::RegisterWindowMessage( "WM_HTML_GETOBJECT" );
::SendMessageTimeout( hWndChild, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000,
(DWORD*)&lRes );

LPFNOBJECTFROMLRESULT pfObjectFromLresult =
(LPFNOBJECTFROMLRESULT)::GetProcAddress( hInst, "ObjectFromLresult" );
if ( pfObjectFromLresult != NULL )
{
HRESULT hr;
hr = (*pfObjectFromLresult)( lRes, IID_IHTMLDocument, 0, (void**)&pDoc );
if ( SUCCEEDED(hr) ) {
IServiceProvider *pService;
hr = pDoc->QueryInterface(IID_IServiceProvider, (void **) &pService);
if ( SUCCEEDED(hr) )
{
hr = pService->QueryService(SID_SWebBrowserApp,
IID_IWebBrowser2, (void **) &pBrowser);

// This is where the problem occurs:
// hr == E_NOINTERFACE
}
}
}

В случае, если это имеет значение, это перспектива а также IE8. (Я подчеркиваю это, потому что оба из них привели к серьезным изменениям в моей кодовой базе, которая отлично работала с XP / IE7.)

Еще раз, моя цель состоит в том, чтобы получить каждый IHTMLElement и взаимодействовать с ним. У меня нет доступа к исходному коду приложения, которое я автоматизирую.

Я рассматриваю отправку нажатия клавиш вслепую Internet Explorer_Server окно, но не хотелось бы.

Отредактировано, чтобы добавить:

Кто-то предложил получить дочерние окна и отправить им сообщения, но я уверен, что это не работает с Internet Explorer_Server; Согласно Spy ++, дочерних окон нет. (Это не зависит от IE. У Java-апплетов тоже нет дочерних окон.)

Обновить

В комментариях Саймон Маурер сказал, что приведенный выше код работает для него, и просто чтобы убедиться в отсутствии опечаток, он очень щедро разместил полное автономное приложение на Pastebin. Когда я использовал его код, он не работал таким же образом в том же месте, и я понял, что он думал, что я хочу подключиться к лежащий в основе страница, а не всплывающее окно. Поэтому я отредактировал текст выше, чтобы устранить эту двусмысленность.

11

Решение

Я не знаю, почему вы хотите получить IServiceProvider или же IWebBrowser2 если вы просто хотите IHTMLElement«S. Вы можете получить их, позвонив IHTMLDocument«s get_all() метод.

Этот фрагмент кода показывает, как это работает:

#include <Windows.h>
#include <mshtml.h>
#include <Exdisp.h>
#include <atlbase.h>
#include <SHLGUID.h>
#include <oleacc.h>
#include <comdef.h>
#include <tchar.h>

HRESULT EnumElements(HINSTANCE hOleAccInst, HWND child)
{
HRESULT hr;

UINT nMsg = ::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));
LRESULT lRes = 0;
::SendMessageTimeout(child, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (PDWORD)&lRes);

LPFNOBJECTFROMLRESULT pfObjectFromLresult = (LPFNOBJECTFROMLRESULT)::GetProcAddress(hOleAccInst, "ObjectFromLresult");
if (pfObjectFromLresult == NULL)
return S_FALSE;

CComPtr<IHTMLDocument2> spDoc;
hr = (*pfObjectFromLresult)(lRes, IID_IHTMLDocument2, 0, (void**)&spDoc);
if (FAILED(hr)) return hr;

CComPtr<IHTMLElementCollection> spElementCollection;
hr = spDoc->get_all(&spElementCollection);
if (FAILED(hr)) return hr;

CComBSTR url;
spDoc->get_URL(&url);
printf("URL: %ws\n", url);

long lElementCount;
hr = spElementCollection->get_length(&lElementCount);
if (FAILED(hr)) return hr;
printf("Number of elements: %d", lElementCount);

VARIANT vIndex; vIndex.vt = VT_I4;
VARIANT vSubIndex; vSubIndex.vt = VT_I4; vSubIndex.lVal = 0;
for (vIndex.lVal = 0; vIndex.lVal < lElementCount; vIndex.lVal++)
{
CComPtr<IDispatch> spDispatchElement;
if (FAILED(spElementCollection->item(vIndex, vSubIndex, &spDispatchElement)))
continue;
CComPtr<IHTMLElement> spElement;
if (FAILED(spDispatchElement->QueryInterface(IID_IHTMLElement, (void**)&spElement)))
continue;
CComBSTR tagName;
if (SUCCEEDED(spElement->get_tagName(&tagName)))
{
printf("%ws\n", tagName);
}
}
return S_OK;
}

int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
HINSTANCE hInst = ::LoadLibrary(_T("OLEACC.DLL"));
if (hInst != NULL)
{
HRESULT hr = EnumElements(hInst, (HWND)0x000F05E4);    // Handle to Internet Explorer_Server determined with Spy++ :)
::FreeLibrary(hInst);
}
::CoUninitialize();
return 0;
}

Выше код работает на обоих: обычное окно или модальное окно, просто передайте правильное HWND к SendMessageTimeout функция.

ПРЕДУПРЕЖДЕНИЕ Я использую жестко закодированный HWND значение в этом примере, если вы хотите проверить это, вы должны запустить экземпляр IE и получить HWND из Internet Explorer_Server окно с использованием Spy ++.

Я также советую вам использовать CComPtr чтобы избежать утечек памяти.

4

Другие решения

Других решений пока нет …