如何自动化弹出模态HTML对话框的IE Web应用程序?

11

[为了更好的清晰度再次修改]

我有一个与网站交互的C++程序。该网站是只能在IE上使用的,我的程序也是如此。

我以一种普通方式(进程外 ——请参见代码)连接到正在运行的IE实例。一旦我获得了 IWebBrowser2,我就可以毫无问题地获取 IHTMLDocument2 并与各个 IHTMLElement 对象进行交互,填写字段并单击按钮。

但如果网页有调用 window.showModalDialog 的javascript,那么我就卡住了:我需要像处理其他页面一样与弹出窗口中的HTML元素进行交互;但似乎我无法获取它的 IWebBrowser2

该弹出窗口始终标题为“Web Page Dialog”,是一个类型为 Internet Explorer_TridentDlgFrame 的窗口,其中包含一个 Internet Explorer_Server。但是,我无法从 Internet Explorer_Server 窗口中获取 IWebBrowser2,就像处理普通IE实例时那样。

我可以获取 IHTMLDocument2Ptr,但是当我尝试获取 IWebBrowser2 时,会返回一个 E_NOINTERFACEHRESULT

代码非常标准,并且在处理“正常”的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
         }
    }
}

如果有关系的话,这是 VistaIE8。(我强调这一点是因为这两个版本对我的代码库引入了破坏性的更改,而该代码库在 XP/IE7 中运行良好。)

再次强调,我的目标是获取每个 IHTMLElement 并与其交互。我无法访问我正在自动化的应用程序的源代码。

考虑到盲目地将按键发送到 Internet Explorer_Server 窗口,但我宁愿不这样做。

编辑添加:

有人建议获取子窗口并向其发送消息,但我相当确定这在 Internet Explorer_Server 上不起作用;根据 Spy++,没有任何子窗口。(这不是 IE 特定的。Java applet 似乎也没有子窗口。)

更新

在评论中,Simon Maurer 说上述代码对他有效,并且为了确保没有错别字,他非常慷慨地在 pastebin 上发布了一个完整的独立应用程序。当我使用他的代码时,在同样的位置以同样的方式失败,我意识到他认为我想连接到 底层 页面而不是弹出窗口。因此,我编辑了上面的文本,以消除这种歧义。


什么是异常?当您调用pDoc->QueryInterface时,pDoc看起来是否有效? - Nate Hekman
@SimonMourier - 我已经修改了问题。是的,它绝对是进程外的。(应用程序的早期版本在进程内工作,但在IE8中停止工作。) 是的,我假设应用程序正在调用showModalDialog。 - egrunin
请注意,我已经在Windows 8上使用IE 10测试了此代码,并且它可以正常工作,即使显示模态对话框。 - Simon Mourier
@SimonMourier - 只是为了确认一下:你是说你用IE10测试了我的代码,对吗?(我没有测试过 - 客户使用的是IE8。) - egrunin
事实上,我也在IE8、Windows 7上进行了测试。我的代码在这里http://pastebin.com/uj1nXG4S,使用的HTML文件只是这个`<html><input type="button" onclick="window.showModalDialog('test.htm');" /></html>`。 - Simon Mourier
显示剩余2条评论
1个回答

4

如果你只是想要获取 IHTMLElement,我不知道为什么你需要获取 IServiceProvider 或者 IWebBrowser2。你可以通过调用 IHTMLDocumentget_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实例并使用Spy ++获取 Internet Explorer_Server 窗口的 HWND
我还建议您使用CComPtr 来避免内存泄漏。

我会试一下。你正在使用哪个版本的IE? - egrunin
是的,这似乎有效!在这个应用程序中,我只需要 spElement,从那里我可以推导出其他所有内容。 - egrunin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接