[再次修改为清楚起见]
我有一个与网站交互的c++程序。该站点是特定于ie的,我的程序也是。
我以一种普通的方式连接到IE的运行实例(退出进程——见代码)。一旦我得到了IWebBrowser2
,我就可以毫无问题地得到IHTMLDocument2
,并与单个IHTMLElement
对象进行交互,填写字段并单击按钮。
但是如果网页有javascript调用window。showModalDialog,我卡住了:我需要与弹出框中的HTML元素进行交互,就像其他页面一样;但我似乎无法理解它的IWebBrowser2
。
这个弹出窗口总是标题为"网页对话框",并且是一个包含Internet Explorer_Server
的Internet Explorer_TridentDlgFrame
类型的窗口。但我不能得到IWebBrowser2从Internet Explorer_Server
窗口的方式,我可以当它是一个正常的IE实例。
我可以得到IHTMLDocument2Ptr
,但是当我试图得到IWebBrowser2
时,我得到的是E_NOINTERFACE
的HRESULT
。
代码是相当标准的东西,并工作良好,如果它是一个"正常"的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和IE8。(我之所以强调这一点,是因为这两种方法都给我的代码库带来了破坏性的变化,而我的代码库在XP/IE7上运行得很好。)
再一次,我的目标是获得每个IHTMLElement
并与之交互。我无法访问我正在自动化的应用程序的源代码。
我正在考虑将按键盲目地发送到Internet Explorer_Server
窗口,但宁愿不。
编辑添加:
有人建议获得子窗口并向他们发送消息,但我很确定这与Internet Explorer_Server
不起作用;根据spy++,没有任何子窗口。(这不是特定于ie的。Java小程序似乎也没有子窗口。)
在评论中,Simon Maurer说上面的代码对他有效,为了确保没有错字,他非常慷慨地在pastebin上发布了一个完整的独立应用程序。当我使用他的代码时,它在相同的地方以同样的方式失败了,我意识到他认为我想连接到页面底层的,而不是弹出窗口。因此,我编辑了上面的文本,以消除歧义。
如果你只想要IHTMLElement
,我不知道为什么你想要IServiceProvider
或IWebBrowser2
,你可以通过调用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: %wsn", 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("%wsn", 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
来避免内存泄漏。