一、簡介
有時,你可能需要一個定製版本的瀏覽器。在這種情況下,你可以自由地把一些新穎但又不標準的特徵增加到一個瀏覽器上。結果,你最終有的只是一個新但不標準的瀏覽器。Web瀏覽器控制項只是瀏覽器的分析引擎。這意味著仍然存在若干的與使用者介面相關的工作等待你做――增加一個地址欄,工具列,記錄,狀態列,頻道欄和收藏夾等。如此,要產生一個定製的瀏覽器,你可以進行兩種類型的編程――一種象微軟把Web瀏覽器控制項轉變成一個功能齊全的瀏覽器如Internet Explorer;一種是在現有的基礎上加一些新的功能。如果有一個直接的方法定製現有的Internet Explorer該多好?BHO(Browser Helper Objects,我譯為"瀏覽器協助者對象",以下皆簡稱BHO)正是用來實現此目的的。
二、關於軟體定製
以前,定製一個軟體的行為主要是通過子類化方法實現的。 通過這種辦法,你可以改變一個視窗的外表與行為。子類化雖然被認為是一種有點暴力方式――受害者根本不知道發生的事情――但它還是長時間以來的唯一的選擇。
隨著微軟Win32 API的到來,進程間子類化不再被鼓勵使用並愈發變得困難起來。當然,如果你是勇敢的--指標從未嚇倒你,而最重要的是,如果你已經遊刃於系統鉤子之間,你可能覺得這一問題太簡單了。 但是情形並不總是這樣。暫放下這點不管,問題在於每一個進程運行在自己的地址空間中,而且打破進程邊界略微有些不正確性。 另一方面, 你可能需要對定製進行更好的管理。更經常情況下,定製可能是程式本身強烈要求實現的。
在後者情況下,已安裝的軟體只需在既定的磁碟位置查詢另外的組件模組,然後裝載、設定初值,最後讓它們自由地按照既定的設計工作。這正是Internet Explorer瀏覽器和它的BHO所要實現的。
三、什麼是BHO?
從某種觀點看,Internet Explorer同普通的Win32程式沒有什麼兩樣。藉助於BHO,你可以寫一個進程內COM對象,這個對象在每次啟動時都要載入。這樣的對象會在與瀏覽器相同的上下文中運行,並能對可用的視窗和模組執行任何行動。例如,一個BHO能夠探測到典型的事件,如GoBack、GoForward、DocumentComplete等;另外BHO能夠存取瀏覽器的菜單與工具列並能做出修改,還能夠產生新視窗來顯示當前網頁的一些額外資訊,還能夠安裝鉤子以監控一些訊息和動作。簡而言之, BHO的工作如我們打入瀏覽器領地的一位間諜(注意這是微軟允許的合法工作)。
在進一步瞭解BHO細節之前,有幾點我需要進一步闡述。首先,BHO對象依託於瀏覽器主視窗。實際上,這意味著一旦一個瀏覽器視窗產生,一個新的BHO對象執行個體就要產生。任何 BHO對象與瀏覽器執行個體的生命週期是一致的。其次, BHO僅存在於Internet Explorer 4.0及以後版本中。
如果你在使用Microsoft Windows? 98, Windows 2000, Windows 95, or Windows NT版本4.0 作業系統的話,也就一塊運行了活動案頭外殼4.71,BHO也被 Windows資源管理員所支援。 BHO是一個COM進程內服務,註冊於註冊表中某一鍵下。在啟動時,Internet Explorer查詢那個鍵並把該鍵下的所有對象預以載入。
Internet Explorer瀏覽器初始化這一對象並要求某一介面功能。如果發現這一介面, Internet Explorer使用其提供的方法傳遞 IUnknown 指標到BHO對象。見圖一:
圖一 ie瀏覽器如何裝入和初始化BHO對象,BHO場所(site)是用於實現通訊的COM介面
瀏覽器可能在註冊表中發現一系列的CLSID,並由此為每個CLSID建立一個進程中執行個體。結果是,這些對象被裝載至瀏覽器上下文中並運行起來,好象它們是本機群組件一樣。但是,由於Internet Explorer的COM特性,即使被裝入到它的進程空間中於事(你的野心實現)也不一定會有多大協助。用另一說法, BHO的確能夠做許多潛在的有用的事情,如子類化組成視窗或者安裝線程局部鉤子,但是它確實遠離瀏覽器的核心活動。為了鉤住瀏覽器的事件或者自動化瀏覽器,BHO需要建立一個私人的基於COM的通訊通道。為此,該BHO應該實現一個稱為IObjectWithSite的介面。事實上,通過介面IobjectWithSite, Internet Explorer 可以傳遞它的IUnknown 介面。BHO反過來能夠儲存該介面並進一步查詢更專門的介面,如IWebBrowser2、IDispatch和IConnectionPointContainer。
另外一種分析BHO對象的途徑與Internet Explorer外殼擴充有關。我們知道,一個WINDOWS外殼擴充即是一個進程內的COM伺服器,它在Windows資源管理員執行某種動作時裝入記憶體――如顯示操作功能表。通過建立一個實現幾個COM介面的COM模組,你就給操作功能表加上一些項並能預以正確處理。一個外殼擴充必須以Windows資源管理員能夠發現的方法註冊。一個BHO對象遵循同樣的模式――唯一的改變在於要實現的介面。然而,儘管實現方式有所不同,外殼擴充與 BHO 仍有許多共同的特點。如下表一:
表一 外殼擴充與 BHO相近特性比較
特性
外殼擴充
BHO對象
載入者
Windows資源管理員
Internet Explorer(和外殼4.17及以上版本的Windows資源管理員)
擊活動作
在某類文檔上的使用者動作(即單擊右鍵)
開啟瀏覽器視窗
何時卸載
參考計數達到0的幾秒之後
導致它載入的視窗關閉時
實現形式
COM進程中DLL
COM 進程中 DLL
註冊需求
常常是為一個COM伺服器設定的入口處,另加的入口依賴於外殼類型及它要應用至的文件類型
常常是為一個COM伺服器設定的入口處,另加一個把它申請為BHO的註冊入口
介面需求
依賴於外殼擴充的類型
IObjectWithSite
如果你對SHELL擴充編程有興趣的話,可以參考MSDN有關資料。
四、BHO的生存周期
前面已經說過,BHO不僅僅為Internet Explorer所支援。如果你在使用外殼 4.71或者更高版本,你的BHO對象也會被Windows資源管理員所載入。下表二展示了我們可以使用的不同版本的外殼產品情況,Windows外殼版本號碼存於庫檔案shell32.dll中。
表二 不同版本的Windows外殼對於BHO的支援情況
外殼版本
安裝的產品
BHO的支援情況
4.00
Windows 95,Windows NT 4.0 帶或不帶 Internet Explorer 4.0 或更老版本。 注意沒有安裝外殼更新
Internet Explorer 4.0
4.71
Windows 95,Windows NT 4.0 帶Internet Explorer 4.0 和活動案頭外殼更新
Internet Explorer 與Windows 資源管理員
4.72
Windows 98
Internet Explorer與Windows 資源管理員
5.00
Windows 2000
Internet Explorer與Windows 資源管理員
BHO對象隨著瀏覽器主視窗的顯示而裝入,隨著瀏覽器主視窗的銷毀而缷載。如果你開啟多個瀏覽器視窗,多個BHO執行個體也一同產生。
無論瀏覽器以什麼樣的命令列啟動,BHO對象都被載入。舉例來說,即使你只是想要見到特定的 HTML 頁或一個給定的檔案夾,BHO對象也被載入。一般地,當 explorer.exe 或 iexplore.exe 啟動並執行時候,BHO都要被考慮在內。如果你設定了"Open each folder in its own window"(對每一個檔案夾以一個獨立視窗開啟)檔案夾選項,那麼你每次開啟一個檔案夾,BHO對象都要被載入。見圖二。
圖二 經過這樣設定,你每次開啟一個檔案夾時,執行一個獨立的explorer.exe執行個體,並裝入登入的BHO對象。
但是注意,這種情形僅適於當你從案頭上的"我的電腦"表徵圖中開啟檔案夾的情況。在這種情況下,每次你移到另外一個檔案夾時外殼都要調用explorer.exe。這種情況在你同時用兩個窗格進行瀏覽時是不會發生的。事實上,當你改變檔案夾時,外殼是不會啟動瀏覽器的新的執行個體的而僅是簡單建立嵌入視圖對象的另外一個執行個體。奇怪的是,如果你在地址欄中輸入一個新的名字來改變檔案夾時,在同一個視窗中同樣可以達到瀏覽之目的,無論Windows資源管理員視圖是單個的還是雙視圖形式。
對於Internet Explorer的情形,事情要更簡單一些。只有你顯式地多次運行iexplore.exe瀏覽器時,你才有多個Internet Explorer的拷貝。當你從Internet Explorer中開啟新的視窗時,每一個視窗在一個新的線程中被複製而不是建立一個新的進程,因此也就不需要重新載入BHO對象。
首先,BHO最有趣的地方是,它是極度動態。每次Windows資源管理員或者Internet Explorer開啟,裝載器從註冊表中讀取已安裝的BHO對象的CLSID然後處理它們。如果你在開啟的瀏覽器多個執行個體中間編輯註冊表的話,你可以隨著多個瀏覽器拷貝的載入而裝入多個不同的BHO。 這就是說,如果你選擇從頭建立一個新的屬於自己的瀏覽器,那麼你可以把它內嵌在一個Visual Basic或者MFC架構視窗中。同時你有相當的機會來靈活安排瀏覽程式。如果它們能滿足你的需要的話,你可以依賴於Internet Explorer的強大的功能並且加上你想要的儘可能多的外掛程式。
五、關於IObjectWithSite介面
從一個高起點來看,BHO即是一個DLL,它能夠依附於Internet Explorer瀏覽器的一個建立的執行個體,在某些情況下也適用於Windows資源管理員。
一般地,一個場所(site)是一個中間對象,它位於容器物件與被包容對象之間。通過它,容器物件管理被包容對象的內容,也因此使得對象的內部功能可用。為此,容器方要實現介面IoleClientSite,被包容對象要實現介面IOleObject 。通過調用IOleObject提供的方法,容器物件使得被包容對象清楚地瞭解其HOST的環境。
一旦容器物件成為Internet Explorer(或是具有WEB能力的Windows資源管理員),被包容對象只需實現一個輕型的IObjectWithSite介面。該介面提供了以下方法:
表三 IObjectWithSite定義
方法
描述
HRESULT SetSite(IUnknown* pUnkSite)
接收ie瀏覽器的IUnknown指標。典型實現是儲存該指標以備將來使用。.
HRESULT GetSite(REFIID riid, void** ppvSite)
從通過SetSite()方法設定的場所中接收並返回指定的介面,典型實現是查詢前面儲存的介面指標以進一步取得指定的介面。
對BHO 的唯一嚴格的要求正在於必須實現這一個介面。 注意你應該避免在調用以上任何一個函數時返回E_NOTIMPL 。 要麼你不實現這一介面,要麼應保證在調用這些方法時進行正確地編碼。
六、構造自己的BHO對象
一個BHO對象就是一個進程中伺服器DLL,選用ATL建立它是再恰當不過的了。我們選擇ATL的另外一個原因是因為它已經提供了預設的而且提供了IObjectWithSite介面的足夠好的實現。另外,在ATL COM 嚮導本地支援的已定義好的物件類型當中,有一個,就是Internet Explorer對象,這正是一個BHO應該具有的類型。一個 ATL Internet Explorer 對象,事實上是一個簡單對象――也就是說,是一個支援IUnknown和自註冊,還有介面IObjectWithSite的COM 伺服器。如果你在ATL工程中添加一個這樣的對象,並調用相應的類CViewSource,你將從嚮導中得到下列代碼:
class ATL_NO_VTABLE CViewSource : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CViewSource, &CLSID_ViewSource>, public IObjectWithSiteImpl<CViewSource>, public IDispatchImpl<IViewSource, &IID_IViewSource, &LIBID_HTMLEDITLib>
正如你所見,嚮導已經使類從介面IObjectWithSiteImpl繼承,這是一個ATL模板類,它提供了介面IObjectWithSite的基本實現。一般情況下,沒有必要重載成員函數GetSite()。取而代之的是, SetSite() 實現代碼經常需要加以定製。ATL實際上僅僅把一個IUnknown介面指標儲存在成員變數m_spUnkSite中。
在文章的剩餘部分,我將討論一個 BHO 的相當複雜而豐富的例子。該BHO對象將依附於Internet Explorer,並顯示一個文字框來顯示當前正瀏覽的網頁源碼。 該代碼視窗將 隨著你改變網頁而自動更新,如果瀏覽器顯示的不是一個HTML網頁時,它將變灰。你對於原始HTML代碼的任何改動立即反映在瀏覽器中。HTML (DHTML)使得這一看似魔術般的實現成為可能。該代碼視窗可被隱藏和通過按動熱鍵重現。 在可見情況下,它與Internet Explorer共用整個案頭空間,見圖三。
圖三 BHO對象在使用中。它依附於Internet Explorer,並顯示一個視窗來顯示當前正瀏覽的網頁源碼。還允許你源碼進行修改。
本例子的關鍵點在於存取Internet Explorer的瀏覽機制,其實它只不過是WebBrowser控制項的一個執行個體而已。這個例子可以分解為以下五步來實現:
- 探測誰在裝入這個對象,是Internet Explorer還是Windows資源管理員;
- 擷取介面IWebBrowser2以實現Web瀏覽器對象;
- 捕捉Web瀏覽器的特定事件;
- 存取當前文檔對象,確定它是一份HTML類型的檔案;
- 管理對話方塊視窗以實現HTML源碼的顯示;
第一個步驟是在DllMain()中完成的。SetSite()是取得指向WebBrowser對象指標的適當位置。請詳細分析以下步驟。
七、探測誰在調用這個對象
如前所述,一個BHO對象會被Internet Explorer或者Windows資源管理員(前提:外殼版本4.71或者更高)所載入。所以我專門設計了一個BHO來處理HTML網頁,因此這個BHO與資源管理員毫無關係。如果一個Dll不想被調用者一起載入,只需在DllMain()中實現了探明誰在調用該對象後返回FALSE即可。參看下面代碼:
if (dwReason == DLL_PROCESS_ATTACH){TCHAR pszLoader[MAX_PATH];//返回調用者模組的名稱,第一個參數應為NULL,詳見msdn。GetModuleFileName(NULL, pszLoader, MAX_PATH);_tcslwr(pszLoader);if (_tcsstr(pszLoader, _T("explorer.exe"))) return FALSE;}
一旦知道了當前進程是Windows資源管理員,可立即退出。
注意,再多加一些條件陳述式是危險的!事實上,另外一些進程試圖裝入該DLL時將被放棄。如果你做另外一個實驗,比方說針對Internet Explorer的執行檔案iexplorer.exe,這時第一個受害者就是regsvr32.exe(該程式用於自動註冊對象)。
if (!_tcsstr(pszLoader, _T("iexplore.exe")))
你不能夠再次註冊該DLL庫了。 事實上,當 regsvr32.exe 試圖裝入DLL以啟用函數DllRegisterServer()時,該調用將被放棄。
八、與Web瀏覽器取得聯絡
SetSite()方法正是BHO對象被初始化的地方,此外,在這個方法中你可以執行所有的僅僅允許發生一次的任務。當你用Internet Explorer開啟一個URL時,你應該等待一系列的事件以確保要求的文檔已完全下載並被初始化。唯有在此時,你才可以通過物件模型暴露的介面(如果存在的話)存取文檔內容。這就是說你要取得一系列的指標。第一個就是指向IWebBrowser2(該介面用來產生WebBrowser對象)的指標。第二個指標與事件有關。該模組必須作為一個瀏覽器的事件接聽程式來實現,目的是為接收下載以及與文檔相關的事件。下面用ATL靈敏指標加以封裝:
CComQIPtr< IWebBrowser2, &IID_IWebBrowser2> m_spWebBrowser2;CComQIPtr<IConnectionPointContainer, &IID_IConnectionPointContainer> m_spCPC;
原始碼部分如下所示:
HRESULT CViewSource::SetSite(IUnknown *pUnkSite){ // 檢索並儲存 IWebBrowser2 指標 m_spWebBrowser2 = pUnkSite; if (m_spWebBrowser2 == NULL) return E_INVALIDARG; //檢索並儲存 IConnectionPointerContainer指標 m_spCPC = m_spWebBrowser2; if (m_spCPC == NULL) return E_POINTER; //檢索並儲存瀏覽器的控制代碼HWND. 並且安裝一個鍵盤鉤子備後用 RetrieveBrowserWindow(); // 為接受事件通知串連到容器 return Connect();}
為了取得IWebBrowser2介面指標,你可以進行查詢。當然也可以在事件剛剛發生時查詢IConnectionPointContainer。這裡,SetSite()檢索了瀏覽器的控制代碼HWND,並且在當前線程中安裝了一個鍵盤鉤子。HWND用於後面Internet Explorer視窗的移動或尺寸調整。這裡的鉤子用來實現熱鍵功能,使用者可以按動熱鍵來顯示/隱藏代碼視窗。
九、從Internet Explorer瀏覽器取得事件
當你導向一個新的URL時,瀏覽器最需要完成的是兩種事件:下載文檔並為之準備HOST環境。也就是說,它必須初始化某對象並使該對象從外部可以利用。針對不同的文件類型,或者裝入一個登入的Microsoft ActiveX? 伺服器來處理該文檔(如Word對於.doc檔案的處理)或者初始化一些內部組件來分析文檔內容並產生和顯示該文檔。對於HTML網頁就是這樣,其內容由於DHTML對象作用而變得可用。當文檔全部下載結束,DownloadComplete事件被啟用。這並不是說,這樣利用物件模型就可以安全地管理文檔的內容了。事實上,DocumentComplete 事件僅指明一切已經結束,文檔已準備好了 (注意DocumentComplete事件僅在你第一次存取URL時到達,如果你執行了重新整理動作,你僅僅收到一個DocumentComplete事件)。
為了截獲瀏覽器發出的事件, BHO需要通過IConnectionPoint 介面串連到瀏覽器上 並且實現傳遞介面IDispatch指標以處理各種事件。現在利用前面取得的IConnectionPointContainer指標來調用FindConnectionPoint方法――它返回一個指標指向連接點對象(正是通過這個連接點對象來取得要求的外向介面,此時是DIID_DWebBrowserEvent2)。 下列代碼顯示了連接點的發生情況:
HRESULT CViewSource::Connect(void){ HRESULT hr; CComPtr<IConnectionPoint> spCP; //為Web瀏覽器事件而接收(receive)連接點 hr = m_spCPC->FindConnectionPoint(DIID_DWebBrowserEvent2, &spCP); if (FAILED(hr)) return hr; // 把事件處理器傳遞到容器。每次事件發生容器都將啟用我們實現的IDispatch介面上的相應的函數。 hr = spCP->Advise( reinterpret_cast<IDispatch*>(this), &m_dwCookie); return hr; }
通過調用介面IConnectionPoint的Advise() 方法, BHO告訴瀏覽器它對它產生的事件高度興趣。 由於COM事件處理機制,所有這些意味著BHO把IDispatch介面指標提供給瀏覽器。瀏覽器將回調IDispatch介面的Invoke() 方法,以事件的ID值作為第一參數:
HRESULT CViewSource::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr){ if (dispidMember == DISPID_DOCUMENTCOMPLETE) { OnDocumentComplete(); m_bDocumentCompleted = true; } :}
切記,當事件不再需要時,應該使之與瀏覽器分離。如果你忘記了做這件事情,BHO對象將被鎖定,即使在你關閉瀏覽器視窗之後。很明顯,實現分離的最佳時機是收到事件OnQuit時。
十、存取文檔對象
此時,該BHO已經有一個參照指向Internet Explorer的Web瀏覽器控制項並被串連到瀏覽器控制項以接收所有它產生的事件。當網頁被全部下載並正確初始化後,我們就可以通過DHTML文檔模型存取它。Web瀏覽器的文件屬性返回一個指向文檔對象的IDispatch介面的指標:
CComPtr<IDispatch> pDisp;HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);
get_Document() 方法取得的僅僅是一個介面指標。我們要進一步確定在IDispatch 指標背後存在一個HTML文檔對象。用VB實現的話,可以用下面代碼:
Dim doc As ObjectSet doc = WebBrowser1.DocumentIf TypeName(doc)="HTMLDocument" Then '' 擷取文檔內容並予以顯示Else '' Disable the display dialogEnd If
現在要瞭解一下get_Document()返回的IDispatch指標 。Internet Explorer不僅僅是一個HTML瀏覽器,而且還是一個ActiveX文檔容器。 這樣一來,難以保證當前瀏覽對象就是一個HTML文檔。不過辦法還是有的――你想,如果IDispatch指標真正指向一個HTML文檔,查詢IHTMLDocument2 介面一定成功。
IHTMLDocument2介面封裝了DHTML物件模型用來展現HTML頁面的所有功能。下面代碼實現這些功能:
CComPtr<IDispatch> pDisp;HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> spHTML;spHTML = pDisp;if (spHTML) { // 擷取文檔內容並予以顯示} else { // disable the Code Window controls}
如果IHTMLDocument2介面查詢失敗,spHTML指標將是NULL。
現在考慮如何獲得當前顯示視窗的原始碼。正如一個HTML頁把它所有的內容封裝在標籤<BODY>中,DHTML物件模型要求你取得一個指向Body對象的指標:
CComPtr<IHTMLElement> m_pBody;hr = spHTML->get_body(&m_pBody);
奇怪的是,DHTML物件模型不讓你取得標籤<BODY>之前的原始內容,如<HEAD>。其內容被處理並存於一些屬性中,但你還是不能從HTML原始檔案中提取這部分的RAW文本。這過,僅從BODY部分取得的內容足夠了。為了取得包含在<BODY>…</BODY>間的HTML代碼部分,可以把outerHTML 屬性內容讀取到一個BSTR變數中:
BSTR bstrHTMLText;hr = m_pBody->get_outerHTML(&bstrHTMLText);
在此基礎上,在代碼視窗中顯示源碼就是一種簡單的事情了:產生一個視窗,進行字元的UNICODE至ANSI轉化和設定編輯框控制項的問題。下面代碼實現這些功能:
HRESULT CViewSource::GetDocumentContent(){ USES_CONVERSION; // 擷取 WebBrowser的文檔對象 CComPtr<IDispatch> pDisp; HRESULT hr = m_spWebBrowser2->get_Document(&pDisp); if (FAILED(hr)) return hr; // 確保我們取得的是一個IHTMLDocument2介面指標 //讓我們查詢一下 IHTMLDocument2 介面 (使用靈敏指標) CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> spHTML; spHTML = pDisp; // 抽取文檔原始碼 if (spHTML) { // 取得BODY 對象 hr = spHTML->get_body(&m_pBody); if (FAILED(hr)) return hr; // 取得HTML 文本 BSTR bstrHTMLText; hr = m_pBody->get_outerHTML(&bstrHTMLText); if (FAILED(hr)) return hr; // 進行文本的Unicode到 ANSI的轉換 LPTSTR psz = new TCHAR[SysStringLen(bstrHTMLText)]; lstrcpy(psz, OLE2T(bstrHTMLText)); // 文本進行相應的調整 HWND hwnd = m_dlgCode.GetDlgItem(IDC_TEXT); EnableWindow(hwnd, true); hwnd = m_dlgCode.GetDlgItem(IDC_APPLY); EnableWindow(hwnd, true); // 設定代碼視窗中的文本 m_dlgCode.SetDlgItemText(IDC_TEXT, psz); delete [] psz; } else // 文檔不是一個 HTML 頁 { m_dlgCode.SetDlgItemText(IDC_TEXT, ""); HWND hwnd = m_dlgCode.GetDlgItem(IDC_TEXT); EnableWindow(hwnd, false); hwnd = m_dlgCode.GetDlgItem(IDC_APPLY); EnableWindow(hwnd, false); } return S_OK; }
因為我要運行這段代碼來響應DocumentComplete事件通知,每個新的頁自動地而且敏捷地被處理。DHTML物件模型使你能夠隨意修改網頁的結構,但這一變化在按F5重新整理後全部複原。你還要處理一下DownloadComplete事件以重新整理代碼視窗 (注意, DownloadComplete 事件發生在 DocumentComplete事件之前)。你應該忽略網頁的首次DownloadComplete事件,而是在執行重新整理動作時才關注這一事件。布爾成員變數m_bDocumentCompleted正是用來區別這兩種情形的。
十一、管理代碼視窗
用來顯示當前HTML頁原始碼的代碼視窗涉及另外一個ATL 基本編程問題-對話方塊視窗,它位於ATL對象嚮導的"Miscellaneous"選項卡下。
我調整了代碼視窗的大小來響應WM_INITDIALOG訊息,使它占居案頭空間的下部地區,正好是在工作列的上面。在瀏覽器啟動時你可以選擇顯示或不顯示這個視窗。預設情況下是顯示的,但這可以通過清除"Show window at startup"複選框項來實現。當然喜歡的話,你可以隨時關閉。按鍵F12即可重新顯示代碼視窗。F12是通過在SetSite()中安裝的鍵盤鉤子實現的。啟動環境存於WINDOWS註冊表中,我選擇外殼庫檔案shlwapi.dll中函數SHGetValue來實現註冊表的讀寫操作。這同使用Reg開頭的Win32函數操作相比,簡單極了。請看:
DWORD dwType, dwVal;DWORD dwSize = sizeof(DWORD);SHGetValue(HKEY_CURRENT_USER, _T("Software\\MSDN\\BHO"), _T("ShowWindowAtStartup"), &dwType, &dwVal, &dwSize);
這個DLL檔案是同Internet Explorer 4.0 和活動案頭的誕生一起產生的,是WIN98及以後版本的標準組成,你可以放心使用。
十二、註冊BHO對象
因為BHO 是一個COM 伺服器,所以既應該作為COM 伺服器註冊又應該作為BHO對象註冊。ATL嚮導自動產生.rgs檔案,第一種情況的註冊就免除了。下面的檔案程式碼片段是用來實現作為BHO對象註冊的(CLSID為例中產生)。
HKLM { SOFTWARE { Microsoft { Windows { CurrentVersion { Explorer { ''BHO'' { ForceRemove {1E1B2879-88FF-11D2-8D96-D7ACAC95951F} }}}}}}}
注意ForceRemove一詞能夠實現在卸載對象時刪除這一行相應的索引值。BHO鍵下聚集了所有的BHO對象。對於這麼多的一串傢伙是從來不作緩衝調用的。這樣以來,安裝與測試BHO就是不費時的事情了。
十三、總結
本文描述了BHO對象,通過它你可以把自己的代碼注入瀏覽器的地址空間中。你必須做的事情是寫一個支援IObjectWithSite 介面的COM 伺服器。在這一點上,你的BHO對象可以實現瀏覽器機制範圍內的各種合法目的。本文所及樣本涉及了COM事件,DHTML物件模型以及WEB瀏覽器編程介面。雖然內容稍寬一些,但它正顯示了現實世界中的BHO對象的應用。如,你想知道瀏覽器在顯示什麼,那麼您就需要瞭解接收事件並要熟悉WEB瀏覽器才行。
另外:Windows資源管理員也是與BHO對象互動的,這一點在編程時要特別注意。本文所附來源程式為MSDN所帶,在Windows2000/VC6下調試通過(編譯通過後,重新啟動IE即得到結果)。