需對 IE 開啟的網頁進行監控、模擬操作、動態提取使用者輸入、動態修改 DOM 內容等,核心在於取得 IHTMLDocument2 並遞迴遍歷 Frame/表單/元素樹。
IWebBrowser2 / IHTMLWindow2 (容器)
└─ IHTMLDocument2 (文件)
├─ IHTMLFramesCollection2 (Frame 集合)
│ └─ IHTMLWindow2 (子 Frame 容器) → 遞迴
└─ IHTMLElementCollection (表單/圖片/連結等集合)
└─ IHTMLFormElement / IHTMLInputElement / ... (具體元素)
| 情境 | 方法 |
|---|---|
| MFC CHtmlView | CHtmlView::GetHtmlDocument() |
| MFC CWebBrowser2 控件 | CWebBrowser2::GetDocument() |
| ATL ActiveX 控件 | IOleClientSite::GetContainer() → QI IHTMLDocument2 |
| MFC ActiveX 控件 | COleControl::GetClientSite() → 同 ATL |
| 獨立 IE 進程 (最常用) | 透過 IShellWindows 列舉所有殼層視窗,Item() → IWebBrowser2 → get_Document() → QI IHTMLDocument2 |
| IE 控件嵌入他人視窗 | FindWindow/EnumChildWindows 找 Internet Explorer_Server 類別 → SendMessage(WM_HTML_GETOBJECT) → ObjectFromLresult() |
#include <atlbase.h>
#include <mshtml.h>
void FindFromShell()
{
CComPtr<IShellWindows> spShellWin;
HRESULT hr = spShellWin.CoCreateInstance(CLSID_ShellWindows);
if (FAILED(hr)) return;
long nCount = 0;
spShellWin->get_Count(&nCount);
for (long i = 0; i < nCount; i++)
{
CComPtr<IDispatch> spDisp;
hr = spShellWin->Item(CComVariant(i), &spDisp);
if (FAILED(hr)) continue;
CComQIPtr<IWebBrowser2> spBrowser(spDisp);
if (!spBrowser) continue;
hr = spBrowser->get_Document(&spDisp);
if (FAILED(hr)) continue;
CComQIPtr<IHTMLDocument2> spDoc(spDisp);
if (!spDoc) continue;
// 已取得 spDoc,可進入遍歷流程
ProcessDocument(spDoc);
}
}
void ProcessDocument(IHTMLDocument2* pDoc)
{
// 1. 處理 Frame 遞迴
CComPtr<IHTMLFramesCollection2> spFrames;
pDoc->get_frames(&spFrames);
if (spFrames)
{
long len = 0;
spFrames->get_length(&len);
for (long i = 0; i < len; i++)
{
CComPtr<IDispatch> spFrameDisp;
spFrames->item(CComVariant(i), CComVariant(), &spFrameDisp);
CComQIPtr<IHTMLWindow2> spWin(spFrameDisp);
if (spWin)
{
CComPtr<IDispatch> spChildDocDisp;
spWin->get_document(&spChildDocDisp);
CComQIPtr<IHTMLDocument2> spChildDoc(spChildDocDisp);
if (spChildDoc) ProcessDocument(spChildDoc);
}
}
}
// 2. 列舉表單
CComPtr<IHTMLElementCollection> spForms;
pDoc->get_forms(&spForms);
if (!spForms) return;
long nFormCount = 0;
spForms->get_length(&nFormCount);
for (long i = 0; i < nFormCount; i++)
{
CComPtr<IDispatch> spFormDisp;
spForms->item(CComVariant(i), CComVariant(), &spFormDisp);
CComQIPtr<IHTMLFormElement> spForm(spFormDisp);
if (!spForm) continue;
// 3. 列舉表單元素
CComPtr<IHTMLElementCollection> spElements;
spForm->get_elements(&spElements);
long nElemCount = 0;
spElements->get_length(&nElemCount);
for (long j = 0; j < nElemCount; j++)
{
CComDispatchDriver spElem; // ATL 智慧指標包裝 IDispatch
spElements->item(CComVariant(j), CComVariant(), &spElem);
CComVariant vName, vValue, vType;
spElem.GetPropertyByName(L"name", &vName);
spElem.GetPropertyByName(L"value", &vValue);
spElem.GetPropertyByName(L"type", &vType);
// 輸出或處理元素屬性
wprintf(L"Form[%d] Elem[%d]: name=%s, value=%s, type=%s\n",
i, j, vName.bstrVal, vValue.bstrVal, vType.bstrVal);
}
}
}
CComQIPtr<IHTMLInputTextElement> 等,效率高、IntelliSense 友善,適合已知頁面結構。CComDispatchDriver::GetPropertyByName,通用性強、程式碼簡潔,適合通用爬蟲/監控工具 (本文採用)。掌握 IShellWindows + IHTMLDocument2 + 遞迴 Frame + CComDispatchDriver 即可構建強大的網頁自動化/監控工具。VC6 編譯通過,歡迎實作延伸。
本文配套完整原始碼可供下載。