Как автоматизировать IE webapp, который появляется модальный диалог HTML?

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

У меня есть программа на С++, которая взаимодействует с веб-сайтом. Сайт является специфичным для 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
         }
    }
}

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

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

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

Отредактировано для добавления:

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

Обновление

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

Ответ 1

Я не знаю, почему вы хотите получить IServiceProvider или IWebBrowser2, если хотите просто IHTMLElement. Вы можете получить их, вызвав метод IHTMLDocument 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, чтобы избежать утечек памяти.