1.引言
如何實現對IE浏覽器中對象的操作是一個很有實際意義問題,通過和IE綁定的DLL我們可以記錄IE浏覽過的網頁的順序,分析用戶的使用行為和模式。我們可以對網頁的內容進行過濾和翻譯,可以自動填寫網頁中經常需要用戶填寫的Form內容等等,我們所有的例子代碼都是通過VC來表示的,采用的原理是通過和IE對象的接口的交互來實現對IE的訪問。實際上是采用COM的技術,我們知道COM是和語言無關的一種二進制對象交互的模式,所以實際上我們下面所描述的內容都可以用其他的語言來實現,比如VB,DELPHI,C++ Builder等等。
2.IE實例遍歷實現
首先我們來看系統是如何知道當前有多少個IE的實例在運行。
我們知道在Windows體系結構下,一個應用程序可以通過操作系統的運行對象表來和這些應用的實例進行交互。但是IE當前的實現機制是不在運行對象表中進行注冊,所以需要采用其他的方法。我們知道可以通過ShellWindows集合來代表屬於shell的當前打開的窗口的集合,而IE就是屬於shell的一個應用程序。
下面我們描述一下用VC實現對當前 IE實例的進行遍歷的方法。IShellWindows是關於系統shell的一個接口,我們可以定義一個如下的接口變量:
SHDocVw::IShellWindowsPtr m_spSHWinds;
然後創建變量的實例:
m_spSHWinds.CreateInstance
(__uuidof(SHDocVw::ShellWindows));
通過IShellWindows接口的方法GetCount
可以得到當前實例的數目:
long nCount = m_spSHWinds- >GetCount();
通過IShellWindows接口的方法Item
可以得到每一個實例對象
IDispatchPtr spDisp;
_variant_t va(i, VT_I4);
spDisp = m_spSHWinds->Item(va);
然後我們可以判斷實例對象是不是
屬於IE浏覽器對象,通過下面的語句實現:
SHDocVw::IWebBrowser2Ptr spBrowser(spDisp);
assert(spBrowser != NULL)
在得到了IE浏覽器對象以後,我們可以調用IWebBrowser2Ptr接口的方法來得到當前的文檔對象的指針: MSHTML::IHTMLDocument2Ptr spDoc(spBrowser->GetDocument());
然後我們就可以通過這個接口對這個文檔對象進行操作,比如通過Gettitle得到文檔的標題。
我們在浏覽網絡的時候,一般總會同時開很多IE的實例,如果這些頁面都是很好的話,我們可能想保存在硬盤上,這樣,我們需要對每一個實例進行保存,而如果我們采用上面的原理,我們可以得到每一個IE的實例及其網頁對象的接口,這樣就可以通過一個簡單的程序來批量的保存當前的所有打開的網頁。采用上面介紹的方法實現了對當前IE實例的遍歷,但是我們希望得到每一個IE實例所產生的事件,這就需要通過DLL的機制來實現。
3.和IE相綁定的DLL的實現
我們介紹一下如何建立和IE進行綁定的DLL的實現的過程。為了和IE的運行實例進行綁定,我們需要建立一個能夠和每一個IE實例進行綁定的DLL。IE的啟動過程是這樣的,當每一個IE的實例啟動的時候,它都會在注冊表中去尋找這個的一個CLSID,具體的注冊表的鍵位置為:
HKEY_LOCALL_MACHINE\SOFTWARE\Microsoft\Windows
\CurrentVersion\Explorer\Browser Helper Objects
當在這個鍵位置下存在CLSIDs的時候,IE會通過使用CoCreateInstance()方法來創建列在該鍵位置下的每一個對象的實例。注意對象的CLSIDs必須用子鍵而非名字值的形式表現,比如{DD41D66E-CE4F-11D2-8DA9-00A0249EABF4} 就是一個有效的子鍵。我們使用DLL的形式而非EXE的形式的原因是因為DLL和IE實例運行在同一個進程空間裡面。每一個這種形式的DLL必須實現接口IObjectWithSite,其中方法SetSite必須被實現。通過這個方法,我們自己的DLL就可以得到一個指向IE COM對象的IUnknown的指針,實際上通過這個指針我們就可以通過COM對象中的方法QueryInterface來遍歷所有可以得到的接口,這是COM的基本的機制。當然我們需要的只是IWebBrowser2這個接口。
實際上我們建立的是一個COM對象,DLL只不過是COM對象的一種表現形式。我們建立的COM對象需要建立和實現的方法有:
1. IOleObjectWithSite接口的方法SetSite必須實現。實際上IE實例通過這個方法向我們的COM對象傳遞一個接口的指針。假設我們有一個接口指針的變量,不妨設為:
CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_myWebBrowser2;
我們就可以在方法SetSite中把這個傳進來的接口指針賦給m_myWebBrowser2。 2. 在我們得到了指向IE COM對象的接口後,我們需要把自己的DLL和IE實例所發生的事件相關連,為了實現這個目的,需要介紹兩個接口:
(1) IConnectionPointContainer。這裡使用這個接口的目的是用來根據它得到的IID來建立和DLL的一個特定的連接。比如我們可以進行如下的定義:
CComQIPtr< IConnectionPointContainer,
&IID_IConnectionPointContainer >
spCPContainer(m_myWebBrowser2);
然後,我們需要把所有IE中發生的事件和我們的DLL進行通訊,可以使用 IConnectPoint。
(2) IConnectPoint。通過這個接口,客戶可以對連接的對象開始或者是終止一個advisory循環。IConnectPoint有兩個主要的方法,一個為Advice,另一個為Unadvise。對於我們的應用來說,Advise是用來在每一個IE發生的事件和DLL之間建立一個通道。而Unadvise就是用來終止以前用Advise建立的通知關系。比如我們可以定義IConnectPoint接口如下: CComPtr< IConnectionPoint > spConnectionPoint;
然後,我們要使所有在IE實例中發生的事件和我們的DLL相關,可以使用 如下的方法:
hr = spCPContainer->FindConnectionPoint(
DIID_DWebBrowserEvents2, &spConnectionPoint);
然後我們通過IConnectPoint接口的方法Advice使每當IE有一個新的事件發生的時候,都能夠讓我們的DLL知道。可以用如下的語句實現:
hr = spConnectionPoint- >Advise(
(IDispatch*)this, &m_dwIDCode);
在把IE實例中的事件和我們的DLL之間建立聯系以後,我們可以通過IDispatch接口的Invoke()方法來處理所有的IE的事件。
3. IDispatch接口的Invoke()方法。IDispatch是從IUnknown中繼承的一個接口的類型,通過COM接口提供的任何服務都可以通過IDispatch接口來實現。IDispatch::Invoke的工作方式同vtbl幕後的工作方式是類似的,Invoke將實現一組按索引來訪問的函數,我們可以對Invoke方法進行動態的定制以提供不同的服務。Invoke方法的表示如下:
STDMETHOD(Invoke)(DISPID dispidMember,REFIID
riid, LCID lcid, WORD wFlags,
DISPPARAMS * pdispparams, VARIANT * pvarResult,
EXCEPINFO * pexcepinfo, UINT * puArgErr);
其中,DISPID是一個長整數,它標識的是一個函數。對於IDispatch的某一個特定的實現,DISPID都是唯一的。IDispatch的每一個實現都有其自己的IID,這裡dispidMemeber實際上是可以認為是和IE實例所發生的每一個事件相關的方法,比如:DISPID_BEFORENAVIGATE2,DISPID_NAVIGATECOMPLETE2等等。 這個方法中另外一個比較重要的參數是DISPPARAMS,它的結構如下:
typedef struct tagDISPPARAMS
{
VARIANTARG* rgvarg;
//VARIANTARG是同VARAIANT相同的,可以在
//OAIDL.IDL中找到。所以實際上rgvarg是一個參數數
//組
DISPID* rgdispidNameArgs; //命名參數的DISPID
unsigned int cArgs; //表示數組中元素的個數
unsigned int CnameArgs; //命名元素的個數
}DISPPARAMS
要注意的是每一個參數的類型都是VARIANTARG,所以在IE和我們DLL之間可以傳遞的參數類型的數目是有限的。只有那些能夠被放到VARIANTARG結構中的類型才可以通過調度接口進行傳遞。 比如對於事件DISPID_NAVIGATECOMPLETE2來說:第一個參數表示IE在訪問的URL的值,類型是VT_BYREF|VT_VARIANT。注意DISPID_NAVIGATECOMPLETE2等DISPID已經在VC中被定義,我們可以直接進行使用。 如上說述,我們在方法Invoke中可以得到所有IE實例所發生的事件,我們可以把這些數據放到文件中進行事後的分析,也可以放到一個列表框中實時的顯示。
4.微軟的HTML文檔對象模型和應用分析
下面我們來看如何得到網頁文檔的接口:網頁文檔的接口為IHTMLDocument2,可以通過調用IE COM對象的get_Document方法來得到網頁的接口。使用如下的語句:
hr = m_spWebBrowser2- >get_Document(&spDisp);
CComQIPtr< IHTMLDocument2,
&IID_IHTMLDocument2 > spHTML;
spHTML = spDisp;
這樣我們就得到了網頁對象的接口,然後我們就可以對網頁進行分析,比如通過IHTMLDocument2提供的方法get_URL我們可以得到和該網頁相關的URL的地址值,通過get_forms方法可以該網頁中所有的Form對象的集合。實際上W3C組織已經制定了一個DOM(Document Objec Model)標准,當然這個標准不僅僅是針對HTML,同時還是針對XML制定的。W3C組織只是定義了網頁對象的接口,不同的公司可以采用不同的語言和方法進行具體的實現。按照W3C組織定義的網頁對象被認為是動態的,即用戶可以動態的對網頁對象裡面所包含的每一個對象進行操作。這裡的對象可以是指一個輸入框,也可以是圖象和聲音等對象。同時按照W3C的正式文檔的說明,網頁對象是可以動態增加和刪除的。事實上,很少有廠商實現了DOM定義的所有功能。微軟對網頁對象的定義也基本上是按照這個標准實現的。但是當前的接口還不支持動態的增加和刪除元素,但是可以對網頁中的基本元素進行屬性的修改。比如IHTMLElementCollection表示網頁中一些基本的元素的集合,IHTMLElement表示網頁中的一個基本的元素。而象IHTMLOptionElement接口就表示一個特定的元素Option。基本的元素都有setAttribute和geAttribute方法來動態的設置和得到元素的名稱和值。
較為常見的一個應用是我們能夠分析網頁中是否有需要填寫的Forms,如果這個網址的Forms以前已經填寫過而且數據我們已經保存下來的話,我們就可以把數據自動放到和該URL下的Forms的相關的位置中去。另外,我們可以總結網頁上需要填寫的Form的數據項,先對這些數據項進行賦值,以後碰到有相同的數據項的時候就自動把我們賦值的內容填寫進去。實際上Form是對象,Form中包含的元素,比如INPUT,OPTION,SELECT等類型的輸入元素都是對象。
另外一個可以想到的應用是自動對網頁中的文本進行翻譯,因為我們可以修改網頁中任何對象的屬性,所以我們可以把裡面不屬於本國語言的部分自動翻譯成本國語言,當然真正的實現還要靠自然語言理解方面技術的突破,但是IE浏覽器的接口和對象的形式使我們能夠靈活的控制整個IE,無論是從事件對象還是到網頁對象。
5.小結
上面我們分析了如何得到所有IE的實例,同時介紹了和IE實例相捆綁的DLL的詳細的實現機制,同時對網頁的對象化進行了分析。並且介紹了幾個相關的應用和實現的方法及存在的技術問題。IE是一個組件化的以COM為基礎的浏覽器,它具有強大的功能,同時為應用開發者留下了廣闊的空間,當然它也存在體積比較大,速度相對比較慢的缺點。但是它的體系結構代表了微軟先進的創新的技術,因此具有強大的生命力。