1、簡介css
有時,你可能須要一個定製版本的瀏覽器。在這種狀況下,你能夠自由地把一些新穎但又不標準的特徵增長到一個瀏覽器上。結果,你最終有的只是一個新但不標準的瀏覽器。Web瀏覽器控件只是瀏覽器的分析引擎。這意味着仍然存在若干的與用戶接口相關的工做等待你作――增長一個地址欄,工具欄,歷史記錄,狀態欄,頻道欄和收藏夾等。如此,要產生一個定製的瀏覽器,你能夠進行兩種類型的編程――一種象微軟把Web瀏覽器控件轉變成一個功能齊全的瀏覽器如Internet Explorer;一種是在現有的基礎上加一些新的功能。若是有一個直接的方法定製現有的Internet Explorer該多好?BHO(Browser Helper Objects,我譯爲"瀏覽器幫助者對象",如下皆簡稱BHO)正是用來實現此目的的。shell
2、關於軟件定製編程
之前,定製一個軟件的行爲主要是經過子類化方法實現的。 經過這種辦法,你能夠改變一個窗口的外表與行爲。子類化雖然被認爲是一種有點暴力方式――受害者根本不知道發生的事情――但它仍是長時間以來的惟一的選擇。api
隨着微軟Win32 API的到來,進程間子類化再也不被鼓勵使用並愈發變得困難起來。固然,若是你是勇敢的--指針從未嚇倒你,而最重要的是,若是你已經遊刃於系統鉤子之間,你可能以爲這一問題太簡單了。 可是情形並不老是這樣。暫放下這點無論,問題在於每個進程運行在本身的地址空間中,並且打破進程邊界略微有些不正確性。 另外一方面, 你可能須要對定製進行更好的管理。更常常狀況下,定製多是程序自己強烈要求實現的。瀏覽器
在後者狀況下,已安裝的軟件只需在既定的磁盤位置查詢另外的組件模塊,而後裝載、設定初值,最後讓它們自由地按照既定的設計工做。這正是Internet Explorer瀏覽器和它的BHO所要實現的。安全
3、什麼是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反過來可以存儲該接口並進一步查詢更專門的接口,如IWebBrowser二、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有關資料。
4、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的強大的功能而且加上你想要的儘量多的插件。
5、關於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 。 要麼你不實現這一接口,要麼應保證在調用這些方法時進行正確地編碼。
6、構造本身的BHO對象
一個BHO對象就是一個進程中服務器DLL,選用ATL建立它是再恰當不過的了。咱們選擇ATL的另一個緣由是由於它已經提供了缺省的並且提供了IObjectWithSite接口的足夠好的實現。另外,在ATL COM 嚮導本地支持的已定義好的對象類型當中,有一個,就是Internet Explorer對象,這正是一個BHO應該具備的類型。一個 ATL Internet Explorer 對象,事實上是一個簡單對象――也就是說,是一個支持IUnknown和自注冊,還有接口IObjectWithSite的COM 服務器。若是你在ATL工程中添加一個這樣的對象,並調用相應的類CViewSource,你將從嚮導中獲得下列代碼:
1.
class
ATL_NO_VTABLE CViewSource :
2.
public
CComObjectRootEx,
3.
public
CComCoClass,
4.
public
IObjectWithSiteImpl,
5.
public
IDispatchImpl
正如你所見,嚮導已經使類從接口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控件的一個實例而已。這個例子能夠分解爲如下五步來實現:
1.探測誰在裝入這個對象,是Internet Explorer仍是Windows資源管理器;
2.獲取接口IWebBrowser2以實現Web瀏覽器對象;
3.捕捉Web瀏覽器的特定事件;
4.存取當前文檔對象,肯定它是一份HTML類型的文件;
5.管理對話框窗口以實現HTML源碼的顯示;
第一個步驟是在DllMain()中完成的。SetSite()是取得指向WebBrowser對象指針的適當位置。請詳細分析如下步驟。
7、探測誰在調用這個對象
如前所述,一個BHO對象會被Internet Explorer或者Windows資源管理器(前提:外殼版本4.71或者更高)所加載。因此我專門設計了一個BHO來處理HTML網頁,所以這個BHO與資源管理器毫無關係。若是一個Dll不想被調用者一塊兒加載,只需在DllMain()中實現了探明誰在調用該對象後返回FALSE便可。參看下面代碼:
01.
if
(dwReason == DLL_PROCESS_ATTACH)
02.
{
03.
TCHAR
pszLoader[MAX_PATH];
04.
05.
//返回調用者模塊的名稱,第一個參數應爲NULL,詳見msdn。
06.
GetModuleFileName(NULL, pszLoader, MAX_PATH);
07.
_tcslwr(pszLoader);
08.
if
(_tcsstr(pszLoader, _T(
"explorer.exe"
)))
09.
return
FALSE;
10.
}
一旦知道了當前進程是Windows資源管理器,可當即退出。
注意,再多加一些條件語句是危險的!事實上,另一些進程試圖裝入該DLL時將被放棄。若是你作另一個試驗,比方說針對Internet Explorer的執行文件iexplorer.exe,這時第一個受害者就是regsvr32.exe(該程序用於自動註冊對象)。
1.
if
(!_tcsstr(pszLoader, _T(
"iexplore.exe"
)))
你不可以再次註冊該DLL庫了。 事實上,當 regsvr32.exe 試圖裝入DLL以激活函數DllRegisterServer()時,該調用將被放棄。
8、與Web瀏覽器取得聯繫
SetSite()方法正是BHO對象被初始化的地方,此外,在這個方法中你能夠執行全部的僅僅容許發生一次的任務。當你用Internet Explorer打開一個URL時,你應該等待一系列的事件以確保要求的文檔已徹底下載並被初始化。惟有在此時,你才能夠經過對象模型暴露的接口(若是存在的話)存取文檔內容。這就是說你要取得一系列的指針。第一個就是指向IWebBrowser2(該接口用來生成WebBrowser對象)的指針。第二個指針與事件有關。該模塊必須做爲一個瀏覽器的事件偵聽器來實現,目的是爲接收下載以及與文檔相關的事件。下面用ATL靈敏指針加以封裝:
1.
CComQIPtr< IWebBrowser2, &IID_IWebBrowser2> m_spWebBrowser2;
2.
CComQIPtr m_spCPC;
源代碼部分以下所示:
01.
HRESULT
CViewSource::SetSite(IUnknown *pUnkSite)
02.
{
03.
// 檢索並存儲 IWebBrowser2 指針
04.
m_spWebBrowser2 = pUnkSite;
05.
if
(m_spWebBrowser2 == NULL)
06.
return
E_INVALIDARG;
07.
//檢索並存儲 IConnectionPointerContainer指針
08.
m_spCPC = m_spWebBrowser2;
09.
if
(m_spCPC == NULL)
10.
return
E_POINTER;
11.
//檢索並存儲瀏覽器的句柄HWND. 而且安裝一個鍵盤鉤子備後用
12.
RetrieveBrowserWindow();
13.
// 爲接受事件通知鏈接到容器
14.
return
Connect();
15.
}
爲了取得IWebBrowser2接口指針,你能夠進行查詢。固然也能夠在事件剛剛發生時查詢IConnectionPointContainer。這裏,SetSite()檢索了瀏覽器的句柄HWND,而且在當前線程中安裝了一個鍵盤鉤子。HWND用於後面Internet Explorer窗口的移動或尺寸調整。這裏的鉤子用來實現熱鍵功能,用戶能夠按動熱鍵來顯示/隱藏代碼窗口。
9、從Internet Explorer瀏覽器取得事件
當你導向一個新的URL時,瀏覽器最須要完成的是兩種事件:下載文檔併爲之準備HOST環境。也就是說,它必須初始化某對象並使該對象從外部能夠利用。針對不一樣的文檔類型,或者裝入一個已註冊的Microsoft ActiveX? 服務器來處理該文檔(如Word對於.doc文件的處理)或者初始化一些內部組件來分析文檔內容並生成和顯示該文檔。對於HTML網頁就是這樣,其內容因爲DHTML對象做用而變得可用。當文檔所有下載結束,DownloadComplete事件被激活。這並非說,這樣利用對象模型就能夠安全地管理文檔的內容了。事實上,DocumentComplete 事件僅指明一切已經結束,文檔已準備好了 (注意DocumentComplete事件僅在你第一次存取URL時到達,若是你執行了刷新動做,你僅僅收到一個DocumentComplete事件)。
爲了截獲瀏覽器發出的事件, BHO須要經過IConnectionPoint 接口鏈接到瀏覽器上 而且實現傳遞接口IDispatch指針以處理各類事件。如今利用前面取得的IConnectionPointContainer指針來調用FindConnectionPoint方法――它返回一個指針指向鏈接點對象(正是經過這個鏈接點對象來取得要求的外向接口,此時是DIID_DWebBrowserEvent2)。 下列代碼顯示了鏈接點的發生狀況:
01.
HRESULT
CViewSource::Connect(
void
)
02.
{
03.
HRESULT
hr;
04.
CComPtr spCP;
05.
//爲Web瀏覽器事件而接收(receive)鏈接點
06.
hr = m_spCPC->FindConnectionPoint(DIID_DWebBrowserEvent2, &spCP);
07.
if
(FAILED(hr))
08.
return
hr;
09.
// 把事件處理器傳遞到容器。每次事件發生容器都將激活咱們實現的IDispatch接口上的相應的函數。
10.
hr = spCP->Advise(
reinterpret_cast
(
this
), &m_dwCookie);
11.
return
hr;
12.
}
經過調用接口IConnectionPoint的Advise() 方法, BHO告訴瀏覽器它對它產生的事件很感興趣。 因爲COM事件處理機制,全部這些意味着BHO把IDispatch接口指針提供給瀏覽器。瀏覽器將回調IDispatch接口的Invoke() 方法,以事件的ID值做爲第一參數:
01.
HRESULT
CViewSource::Invoke(DISPID dispidMember, REFIID riid,
02.
LCID
lcid,
WORD
wFlags, DISPPARAMS* pDispParams,
03.
VARIANT* pvarResult, EXCEPINFO* pExcepInfo,
UINT
* puArgErr)
04.
{
05.
if
(dispidMember == DISPID_DOCUMENTCOMPLETE) {
06.
OnDocumentComplete();
07.
m_bDocumentCompleted =
true
;
08.
}
09.
:
10.
}
切記,當事件再也不須要時,應該使之與瀏覽器分離。若是你忘記了作這件事情,BHO對象將被鎖定,即便在你關閉瀏覽器窗口以後。很明顯,實現分離的最佳時機是收到事件OnQuit時。
10、存取文檔對象
此時,該BHO已經有一個參照指向Internet Explorer的Web瀏覽器控件並被鏈接到瀏覽器控件以接收全部它產生的事件。當網頁被所有下載並正確初始化後,咱們就能夠經過DHTML文檔模型存取它。Web瀏覽器的文檔屬性返回一個指向文檔對象的IDispatch接口的指針:
1.
CComPtr pDisp;
2.
HRESULT
hr = m_spWebBrowser2->get_Document(&pDisp);
get_Document() 方法取得的僅僅是一個接口指針。咱們要進一步肯定在IDispatch 指針背後存在一個HTML文檔對象。用VB實現的話,能夠用下面代碼:
1.
Dim doc As Object
2.
Set doc = WebBrowser1.Document
3.
If TypeName(doc)=
"HTMLDocument"
Then
4.
''
獲取文檔內容並予以顯示
5.
Else
6.
''
Disable the display dialog
7.
End If
如今要了解一下get_Document()返回的IDispatch指針 。Internet Explorer不只僅是一個HTML瀏覽器,並且仍是一個ActiveX文檔容器。 這樣一來,難以保證當前瀏覽對象就是一個HTML文檔。不過辦法仍是有的――你想,若是IDispatch指針真正指向一個HTML文檔,查詢IHTMLDocument2 接口必定成功。
IHTMLDocument2接口包裝了DHTML對象模型用來展示HTML頁面的全部功能。下面代碼實現這些功能:
01.
CComPtr pDisp;
02.
HRESULT
hr = m_spWebBrowser2->get_Document(&pDisp);
03.
CComQIPtr spHTML;
04.
spHTML = pDisp;
05.
if
(spHTML) {
06.
// 獲取文檔內容並予以顯示
07.
}
08.
else
{
09.
// disable the Code Window controls
10.
}
若是IHTMLDocument2接口查詢失敗,spHTML指針將是NULL。
如今考慮如何得到當前顯示窗口的源代碼。正如一個HTML頁把它全部的內容封裝在標籤中,DHTML對象模型要求你取得一個指向Body對象的指針:
1.
CComPtr m_pBody;
2.
hr = spHTML->get_body(&m_pBody);
奇怪的是,DHTML對象模型不讓你取得標籤以前的原始內容,如。其內容被處理並存於一些屬性中,但你仍是不能從HTML原始文件中提取這部分的RAW文本。這過,僅從BODY部分取得的內容足夠了。爲了取得包含在…間的HTML代碼部分,能夠把outerHTML屬性內容讀取到一個BSTR變量中:
1.
BSTR bstrHTMLText;
2.
hr = m_pBody->get_outerHTML(&bstrHTMLText);
在此基礎上,在代碼窗口中顯示源碼就是一種簡單的事情了:生成一個窗口,進行字符的UNICODE至ANSI轉化和設置編輯框控件的問題。下面代碼實現這些功能:
01.
HRESULT
CViewSource::GetDocumentContent()
02.
{
03.
USES_CONVERSION;
04.
05.
// 獲取 WebBrowser的文檔對象
06.
CComPtr pDisp;
07.
HRESULT
hr = m_spWebBrowser2->get_Document(&pDisp);
08.
if
(FAILED(hr))
09.
return
hr;
10.
11.
// 確保咱們取得的是一個IHTMLDocument2接口指針
12.
//讓咱們查詢一下 IHTMLDocument2 接口 (使用靈敏指針)
13.
CComQIPtr spHTML;
14.
spHTML = pDisp;
15.
16.
// 抽取文檔源代碼
17.
if
(spHTML)
18.
{
19.
// 取得BODY 對象
20.
hr = spHTML->get_body(&m_pBody);
21.
if
(FAILED(hr))
22.
return
hr;
23.
// 取得HTML 文本
24.
BSTR bstrHTMLText;
25.
hr = m_pBody->get_outerHTML(&bstrHTMLText);
26.
if
(FAILED(hr))
27.
return
hr;
28.
// 進行文本的Unicode到 ANSI的轉換
29.
LPTSTR
psz =
new
TCHAR
[SysStringLen(bstrHTMLText)];
30.
lstrcpy(psz, OLE2T(bstrHTMLText));
31.
// 文本進行相應的調整
32.
HWND
hwnd = m_dlgCode.GetDlgItem(IDC_TEXT);
33.
EnableWindow(hwnd,
true
);
34.
hwnd = m_dlgCode.GetDlgItem(IDC_APPLY);
35.
EnableWindow(hwnd,
true
);
36.
37.
// 設置代碼窗口中的文本
38.
m_dlgCode.SetDlgItemText(IDC_TEXT, psz);
39.
delete
[] psz;
40.
}
41.
else
// 文檔不是一個 HTML 頁
42.
{
43.
m_dlgCode.SetDlgItemText(IDC_TEXT,
""
);
44.
HWND
hwnd = m_dlgCode.GetDlgItem(IDC_TEXT);
45.
EnableWindow(hwnd,
false
);
46.
hwnd = m_dlgCode.GetDlgItem(IDC_APPLY);
47.
EnableWindow(hwnd,
false
);
48.
}
49.
50.
return
S_OK;
51.
}
由於我要運行這段代碼來響應DocumentComplete事件通知,每一個新的頁自動地並且敏捷地被處理。DHTML對象模型使你可以隨意修改網頁的結構,但這一變化在按F5刷新後所有復原。你還要處理一下DownloadComplete事件以刷新代碼窗口 (注意, DownloadComplete 事件發生在 DocumentComplete事件以前)。你應該忽略網頁的首次DownloadComplete事件,而是在執行刷新動做時才關注這一事件。布爾成員變量m_bDocumentCompleted正是用來區別這兩種情形的。
11、管理代碼窗口
用來顯示當前HTML頁原始碼的代碼窗口涉及另一個ATL 基本編程問題-對話框窗口,它位於ATL對象嚮導的"Miscellaneous"選項卡下。
我調整了代碼窗口的大小來響應WM_INITDIALOG消息,使它佔居桌面空間的下部區域,正好是在任務欄的上面。在瀏覽器啓動時你能夠選擇顯示或不顯示這個窗口。缺省狀況下是顯示的,但這能夠經過清除"Show window at startup"複選框項來實現。固然喜歡的話,你能夠隨時關閉。按鍵F12便可從新顯示代碼窗口。F12是經過在SetSite()中安裝的鍵盤鉤子實現的。啓動環境存於WINDOWS註冊表中,我選擇外殼庫文件shlwapi.dll中函數SHGetValue來實現註冊表的讀寫操做。這同使用Reg開頭的Win32函數操做相比,簡單極了。請看:
1.
DWORD
dwType, dwVal;
2.
DWORD
dwSize =
sizeof
(
DWORD
);
3.
SHGetValue(HKEY_CURRENT_USER, _T(
"Software\\MSDN\\BHO"
), _T(
"ShowWindowAtStartup"
), &dwType, &dwVal, &dwSize);
這個DLL文件是同Internet Explorer 4.0 和活動桌面的誕生一塊兒產生的,是WIN98及之後版本的標準組成,你能夠放心使用。
12、註冊BHO對象
由於BHO 是一個COM 服務器,因此既應該做爲COM 服務器註冊又應該做爲BHO對象註冊。ATL嚮導自動生成.rgs文件,第一種狀況的註冊就免除了。下面的文件代碼段是用來實現做爲BHO對象註冊的(CLSID爲例中生成)。
01.
HKLM {
02.
SOFTWARE {
03.
Microsoft {
04.
Windows {
05.
CurrentVersion {
06.
Explorer {
07.
''
BHO
''
{
08.
ForceRemove {1E1B2879-88FF-11D2-8D96-D7ACAC95951F}
09.
}}}}}}}
注意ForceRemove一詞可以實如今卸載對象時刪除這一行相應的鍵值。BHO鍵下彙集了全部的BHO對象。對於這麼多的一串傢伙是歷來不做緩衝調用的。這樣以來,安裝與測試BHO就是不費時的事情了。
十3、總結
本文描述了BHO對象,經過它你能夠把本身的代碼注入瀏覽器的地址空間中。你必須作的事情是寫一個支持IObjectWithSite 接口的COM 服務器。在這一點上,你的BHO對象能夠實現瀏覽器機制範圍內的各類合法目的。本文所及示例涉及了COM事件,DHTML對象模型以及WEB瀏覽器編程接口。雖然內容稍寬一些,但它正顯示了現實世界中的BHO對象的應用。如,你想知道瀏覽器在顯示什麼,那麼您就須要瞭解接收事件並要熟悉WEB瀏覽器才行。
另外:Windows資源管理器也是與BHO對象交互的,這一點在編程時要特別注意。本文所附源程序爲MSDN所帶,在Windows2000/VC6下調試經過(編譯經過後,從新啓動IE即獲得結果)。