日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

VC知識(shí)庫文章 - 如何使用BHO定制你的Internet Explorer瀏覽器

 xiaoqdu 2008-11-24

如何使用BHO定制你的Internet Explorer瀏覽器

原文:微軟公司 Dino Esposito
編譯:朱先中

原文出處:Browser Helper Objects: The Browser the Way You Want It

一、簡介
有時(shí),你可能需要一個(gè)定制版本的瀏覽器。在這種情況下,你可以自由地把一些新穎但又不標(biāo)準(zhǔn)的特征增加到一個(gè)瀏覽器上。結(jié)果,你最終有的只是一個(gè)新但不 標(biāo)準(zhǔn)的瀏覽器。Web瀏覽器控件只是瀏覽器的分析引擎。這意味著仍然存在若干的與用戶接口相關(guān)的工作等待你做――增加一個(gè)地址欄,工具欄,歷史記錄,狀態(tài) 欄,頻道欄和收藏夾等。如此,要產(chǎn)生一個(gè)定制的瀏覽器,你可以進(jìn)行兩種類型的編程――一種象微軟把Web瀏覽器控件轉(zhuǎn)變成一個(gè)功能齊全的瀏覽器如 Internet Explorer;一種是在現(xiàn)有的基礎(chǔ)上加一些新的功能。如果有一個(gè)直接的方法定制現(xiàn)有的Internet Explorer該多好?BHO(Browser Helper Objects,我譯為"瀏覽器幫助者對(duì)象",以下皆簡稱BHO)正是用來實(shí)現(xiàn)此目的的。

二、關(guān)于軟件定制
以前,定制一個(gè)軟件的行為主要是通過子類化方法實(shí)現(xiàn)的。 通過這種辦法,你可以改變一個(gè)窗口的外表與行為。子類化雖然被認(rèn)為是一種有點(diǎn)暴力方式――受害者根本不知道發(fā)生的事情――但它還是長時(shí)間以來的唯一的選擇。
隨著微軟Win32 API的到來,進(jìn)程間子類化不再被鼓勵(lì)使用并愈發(fā)變得困難起來。當(dāng)然,如果你是勇敢的--指針從未嚇倒你,而最重要的是,如果你已經(jīng)游刃于系統(tǒng)鉤子之間,你可能覺得這一問題太簡單了。 但是情形并不總是這樣。暫放下這點(diǎn)不管,問題在于每一個(gè)進(jìn)程運(yùn)行在自己的地址空間中,而且打破進(jìn)程邊界略微有些不正確性。 另一方面, 你可能需要對(duì)定制進(jìn)行更好的管理。更經(jīng)常情況下,定制可能是程序本身強(qiáng)烈要求實(shí)現(xiàn)的。
在后者情況下,已安裝的軟件只需在既定的磁盤位置查詢另外的組件模塊,然后裝載、設(shè)定初值,最后讓它們自由地按照既定的設(shè)計(jì)工作。這正是Internet Explorer瀏覽器和它的BHO所要實(shí)現(xiàn)的。

三、什么是BHO?
從某種觀點(diǎn)看,Internet Explorer同普通的Win32程序沒有什么兩樣。借助于BHO,你可以寫一個(gè)進(jìn)程內(nèi)COM對(duì)象,這個(gè)對(duì)象在每次啟動(dòng)時(shí)都要加載。這樣的對(duì)象會(huì)在與瀏 覽器相同的上下文中運(yùn)行,并能對(duì)可用的窗口和模塊執(zhí)行任何行動(dòng)。例如,一個(gè)BHO能夠探測(cè)到典型的事件,如GoBack、GoForward、 DocumentComplete等;另外BHO能夠存取瀏覽器的菜單與工具欄并能做出修改,還能夠產(chǎn)生新窗口來顯示當(dāng)前網(wǎng)頁的一些額外信息,還能夠安裝 鉤子以監(jiān)控一些消息和動(dòng)作。簡而言之, BHO的工作如我們打入瀏覽器領(lǐng)地的一位間諜(注意這是微軟允許的合法工作)。
在進(jìn)一步了解BHO細(xì)節(jié)之前,有幾點(diǎn)我需要進(jìn)一步闡述。首先,BHO對(duì)象依托于瀏覽器主窗口。實(shí)際上,這意味著一旦一個(gè)瀏覽器窗口產(chǎn)生,一個(gè)新的BHO對(duì)象實(shí)例就要生成。任何 BHO對(duì)象與瀏覽器實(shí)例的生命周期是一致的。其次, BHO僅存在于Internet Explorer 4.0及以后版本中。
如果你在使用Microsoft Windows? 98, Windows 2000, Windows 95, or Windows NT版本4.0 操作系統(tǒng)的話,也就一塊運(yùn)行了活動(dòng)桌面外殼4.71,BHO也被 Windows資源管理器所支持。 BHO是一個(gè)COM進(jìn)程內(nèi)服務(wù),注冊(cè)于注冊(cè)表中某一鍵下。在啟動(dòng)時(shí),Internet Explorer查詢那個(gè)鍵并把該鍵下的所有對(duì)象預(yù)以加載。
Internet Explorer瀏覽器初始化這一對(duì)象并要求某一接口功能。如果發(fā)現(xiàn)這一接口, Internet Explorer使用其提供的方法傳遞 IUnknown 指針到BHO對(duì)象。見圖一:


圖一 ie瀏覽器如何裝入和初始化BHO對(duì)象,BHO場(chǎng)所(site)是用于實(shí)現(xiàn)通信的COM接口

瀏覽器可能在注冊(cè)表中發(fā)現(xiàn)一系列的CLSID,并由此為每個(gè)CLSID建立一個(gè)進(jìn)程中實(shí)例。結(jié)果是,這些對(duì)象被裝載至瀏覽器上下文中并運(yùn)行起來,好象 它們是本地組件一樣。但是,由于Internet Explorer的COM特性,即使被裝入到它的進(jìn)程空間中于事(你的野心實(shí)現(xiàn))也不一定會(huì)有多大幫助。用另一說法, BHO的確能夠做許多潛在的有用的事情,如子類化組成窗口或者安裝線程局部鉤子,但是它確實(shí)遠(yuǎn)離瀏覽器的核心活動(dòng)。為了鉤住瀏覽器的事件或者自動(dòng)化瀏覽 器,BHO需要建立一個(gè)私有的基于COM的通訊通道。為此,該BHO應(yīng)該實(shí)現(xiàn)一個(gè)稱為IObjectWithSite的接口。事實(shí)上,通過接口 IobjectWithSite, Internet Explorer 可以傳遞它的IUnknown 接口。BHO反過來能夠存儲(chǔ)該接口并進(jìn)一步查詢更專門的接口,如IWebBrowser2、IDispatch和 IConnectionPointContainer。
  另外一種分析BHO對(duì)象的途徑與Internet Explorer外殼擴(kuò)展有關(guān)。我們知道,一個(gè)WINDOWS外殼擴(kuò)展即是一個(gè)進(jìn)程內(nèi)的COM服務(wù)器,它在Windows資源管理器執(zhí)行某種動(dòng)作時(shí)裝入內(nèi) 存――如顯示上下文菜單。通過建立一個(gè)實(shí)現(xiàn)幾個(gè)COM接口的COM模塊,你就給上下文菜單加上一些項(xiàng)并能預(yù)以正確處理。一個(gè)外殼擴(kuò)展必須以Windows 資源管理器能夠發(fā)現(xiàn)的方法注冊(cè)。一個(gè)BHO對(duì)象遵循同樣的模式――唯一的改變?cè)谟谝獙?shí)現(xiàn)的接口。然而,盡管實(shí)現(xiàn)方式有所不同,外殼擴(kuò)展與 BHO 仍有許多共同的特點(diǎn)。如下表一:

表一 外殼擴(kuò)展與 BHO相近特性比較

特性 外殼擴(kuò)展 BHO對(duì)象
加載者 Windows資源管理器 Internet Explorer(和外殼4.17及以上版本的Windows資源管理器)
擊活動(dòng)作 在某類文檔上的用戶動(dòng)作(即單擊右鍵) 打開瀏覽器窗口
何時(shí)卸載 參考計(jì)數(shù)達(dá)到0的幾秒之后 導(dǎo)致它加載的窗口關(guān)閉時(shí)
實(shí)現(xiàn)形式 COM進(jìn)程中DLL COM 進(jìn)程中 DLL
注冊(cè)需求 常常是為一個(gè)COM服務(wù)器設(shè)置的入口處,另加的入口依賴于外殼類型及它要應(yīng)用至的文檔類型 常常是為一個(gè)COM服務(wù)器設(shè)置的入口處,另加一個(gè)把它申請(qǐng)為BHO的注冊(cè)入口
接口需求 依賴于外殼擴(kuò)展的類型 IObjectWithSite

如果你對(duì)SHELL擴(kuò)展編程有興趣的話,可以參考MSDN有關(guān)資料。

四、BHO的生存周期
前面已經(jīng)說過,BHO不僅僅為Internet Explorer所支持。如果你在使用外殼 4.71或者更高版本,你的BHO對(duì)象也會(huì)被Windows資源管理器所加載。下表二展示了我們可以使用的不同版本的外殼產(chǎn)品情況,Windows外殼版 本號(hào)存于庫文件shell32.dll中。

表二 不同版本的Windows外殼對(duì)于BHO的支持情況

外殼版本 安裝的產(chǎn)品 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 和活動(dòng)桌面外殼更新  Internet Explorer 與Windows 資源管理器
4.72  Windows 98  Internet Explorer與Windows 資源管理器
5.00  Windows 2000  Internet Explorer與Windows 資源管理器

BHO對(duì)象隨著瀏覽器主窗口的顯示而裝入,隨著瀏覽器主窗口的銷毀而缷載。如果你打開多個(gè)瀏覽器窗口,多個(gè)BHO實(shí)例也一同產(chǎn)生。
無論瀏覽器以什么樣的命令行啟動(dòng),BHO對(duì)象都被加載。舉例來說,即使你只是想要見到特定的 HTML 頁或一個(gè)給定的文件夾,BHO對(duì)象也被加載。一般地,當(dāng) explorer.exe 或 iexplore.exe 運(yùn)行的時(shí)候,BHO都要被考慮在內(nèi)。如果你設(shè)置了"Open each folder in its own window"(對(duì)每一個(gè)文件夾以一個(gè)獨(dú)立窗口打開)文件夾選項(xiàng),那么你每次打開一個(gè)文件夾,BHO對(duì)象都要被加載。見圖二。


圖二 經(jīng)過這樣設(shè)置,你每次打開一個(gè)文件夾時(shí),執(zhí)行一個(gè)獨(dú)立的explorer.exe實(shí)例,并裝入已注冊(cè)的BHO對(duì)象。

但是注意,這種情形僅適于當(dāng)你從桌面上的"我的電腦"圖標(biāo)中打開文件夾的情況。在這種情況下,每次你移到另外一個(gè)文件夾時(shí)外殼都要調(diào)用 explorer.exe。這種情況在你同時(shí)用兩個(gè)窗格進(jìn)行瀏覽時(shí)是不會(huì)發(fā)生的。事實(shí)上,當(dāng)你改變文件夾時(shí),外殼是不會(huì)啟動(dòng)瀏覽器的新的實(shí)例的而僅是簡單 創(chuàng)建嵌入視圖對(duì)象的另外一個(gè)實(shí)例。奇怪的是,如果你在地址欄中輸入一個(gè)新的名字來改變文件夾時(shí),在同一個(gè)窗口中同樣可以達(dá)到瀏覽之目的,無論 Windows資源管理器視圖是單個(gè)的還是雙視圖形式。
對(duì)于Internet Explorer的情形,事情要更簡單一些。只有你顯式地多次運(yùn)行iexplore.exe瀏覽器時(shí),你才有多個(gè)Internet Explorer的拷貝。當(dāng)你從Internet Explorer中打開新的窗口時(shí),每一個(gè)窗口在一個(gè)新的線程中被復(fù)制而不是創(chuàng)建一個(gè)新的進(jìn)程,因此也就不需要重新載入BHO對(duì)象。
首先,BHO最有趣的地方是,它是極度動(dòng)態(tài)的。每次Windows資源管理器或者Internet Explorer打開,裝載器從注冊(cè)表中讀取已安裝的BHO對(duì)象的CLSID然后處理它們。如果你在打開的瀏覽器多個(gè)實(shí)例中間編輯注冊(cè)表的話,你可以隨著 多個(gè)瀏覽器拷貝的載入而裝入多個(gè)不同的BHO。 這就是說,如果你選擇從頭創(chuàng)建一個(gè)新的屬于自己的瀏覽器,那么你可以把它內(nèi)嵌在一個(gè)Visual Basic或者M(jìn)FC框架窗口中。同時(shí)你有相當(dāng)?shù)臋C(jī)會(huì)來靈活安排瀏覽程序。如果它們能滿足你的需要的話,你可以依賴于Internet Explorer的強(qiáng)大的功能并且加上你想要的盡可能多的插件。

五、關(guān)于IObjectWithSite接口
從一個(gè)高起點(diǎn)來看,BHO即是一個(gè)DLL,它能夠依附于Internet Explorer瀏覽器的一個(gè)新建的實(shí)例,在某些情況下也適用于Windows資源管理器。
一般地,一個(gè)場(chǎng)所(site)是一個(gè)中間對(duì)象,它位于容器對(duì)象與被包容對(duì)象之間。通過它,容器對(duì)象管理被包容對(duì)象的內(nèi)容,也因此使得對(duì)象的內(nèi)部功能可用。為此,容器方要實(shí)現(xiàn)接口IoleClientSite,被包容對(duì)象要實(shí)現(xiàn)接口IOleObject 。通過調(diào)用IOleObject提供的方法,容器對(duì)象使得被包容對(duì)象清楚地了解其HOST的環(huán)境。
一旦容器對(duì)象成為Internet Explorer(或是具有WEB能力的Windows資源管理器),被包容對(duì)象只需實(shí)現(xiàn)一個(gè)輕型的IObjectWithSite接口。該接口提供了以下方法:

表三 IObjectWithSite定義

方法 描述
HRESULT SetSite(IUnknown* pUnkSite) 接收ie瀏覽器的IUnknown指針。典型實(shí)現(xiàn)是保存該指針以備將來使用。.
HRESULT GetSite(REFIID riid, void** ppvSite) 從通過SetSite()方法設(shè)置的場(chǎng)所中接收并返回指定的接口,典型實(shí)現(xiàn)是查詢前面保存的接口指針以進(jìn)一步取得指定的接口。

對(duì)BHO 的唯一嚴(yán)格的要求正在于必須實(shí)現(xiàn)這一個(gè)接口。 注意你應(yīng)該避免在調(diào)用以上任何一個(gè)函數(shù)時(shí)返回E_NOTIMPL 。 要么你不實(shí)現(xiàn)這一接口,要么應(yīng)保證在調(diào)用這些方法時(shí)進(jìn)行正確地編碼。

六、構(gòu)造自己的BHO對(duì)象
一個(gè)BHO對(duì)象就是一個(gè)進(jìn)程中服務(wù)器DLL,選用ATL創(chuàng)建它是再恰當(dāng)不過的了。我們選擇ATL的另外一個(gè)原因是因?yàn)樗呀?jīng)提供了缺省的而且提供了IObjectWithSite接口的足夠好的實(shí)現(xiàn)。另外,在ATL COM 向?qū)П镜刂С值囊讯x好的對(duì)象類型當(dāng)中,有一個(gè),就是Internet Explorer對(duì)象,這正是一個(gè)BHO應(yīng)該具有的類型。一個(gè) ATL Internet Explorer 對(duì)象,事實(shí)上是一個(gè)簡單對(duì)象――也就是說,是一個(gè)支持IUnknown和自注冊(cè),還有接口IObjectWithSite的COM 服務(wù)器。如果你在ATL工程中添加一個(gè)這樣的對(duì)象,并調(diào)用相應(yīng)的類CViewSource,你將從向?qū)е械玫较铝写a:

class ATL_NO_VTABLE CViewSource :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CViewSource, &CLSID_ViewSource>,
public IObjectWithSiteImpl<CViewSource>,
public IDispatchImpl<IViewSource, &IID_IViewSource,
&LIBID_HTMLEDITLib>      
正如你所見,向?qū)б呀?jīng)使類從接口IObjectWithSiteImpl繼承,這是一個(gè)ATL模板類,它提供了接口IObjectWithSite的基本實(shí)現(xiàn)。一般情況下,沒有必要重載成員函數(shù)GetSite()。取而代之的是, SetSite() 實(shí)現(xiàn)代碼經(jīng)常需要加以定制。ATL實(shí)際上僅僅把一個(gè)IUnknown接口指針存儲(chǔ)在成員變量m_spUnkSite中。
在文章的剩余部分,我將討論一個(gè) BHO 的相當(dāng)復(fù)雜而豐富的例子。該BHO對(duì)象將依附于Internet Explorer,并顯示一個(gè)文本框來顯示當(dāng)前正瀏覽的網(wǎng)頁源碼。 該代碼窗口將 隨著你改變網(wǎng)頁而自動(dòng)更新,如果瀏覽器顯示的不是一個(gè)HTML網(wǎng)頁時(shí),它將變灰。你對(duì)于原始HTML代碼的任何改動(dòng)立即反映在瀏覽器中。HTML (DHTML)使得這一看似魔術(shù)般的實(shí)現(xiàn)成為可能。該代碼窗口可被隱藏和通過按動(dòng)熱鍵重現(xiàn)。 在可見情況下,它與Internet Explorer共享整個(gè)桌面空間,見圖三。


圖三 BHO對(duì)象在使用中。它依附于Internet Explorer,并顯示一個(gè)窗口來顯示當(dāng)前正瀏覽的網(wǎng)頁源碼。還允許你源碼進(jìn)行修改。

本例子的關(guān)鍵點(diǎn)在于存取Internet Explorer的瀏覽機(jī)制,其實(shí)它只不過是WebBrowser控件的一個(gè)實(shí)例而已。這個(gè)例子可以分解為以下五步來實(shí)現(xiàn):
  1. 探測(cè)誰在裝入這個(gè)對(duì)象,是Internet Explorer還是Windows資源管理器;
  2. 獲取接口IWebBrowser2以實(shí)現(xiàn)Web瀏覽器對(duì)象;
  3. 捕捉Web瀏覽器的特定事件;
  4. 存取當(dāng)前文檔對(duì)象,確定它是一份HTML類型的文件;
  5. 管理對(duì)話框窗口以實(shí)現(xiàn)HTML源碼的顯示;

  第一個(gè)步驟是在DllMain()中完成的。SetSite()是取得指向WebBrowser對(duì)象指針的適當(dāng)位置。請(qǐng)?jiān)敿?xì)分析以下步驟。

七、探測(cè)誰在調(diào)用這個(gè)對(duì)象
如前所述,一個(gè)BHO對(duì)象會(huì)被Internet Explorer或者Windows資源管理器(前提:外殼版本4.71或者更高)所加載。所以我專門設(shè)計(jì)了一個(gè)BHO來處理HTML網(wǎng)頁,因此這個(gè) BHO與資源管理器毫無關(guān)系。如果一個(gè)Dll不想被調(diào)用者一起加載,只需在DllMain()中實(shí)現(xiàn)了探明誰在調(diào)用該對(duì)象后返回FALSE即可。參看下面 代碼:

if (dwReason == DLL_PROCESS_ATTACH)
{
TCHAR pszLoader[MAX_PATH];
//返回調(diào)用者模塊的名稱,第一個(gè)參數(shù)應(yīng)為NULL,詳見msdn。
GetModuleFileName(NULL, pszLoader, MAX_PATH);
_tcslwr(pszLoader);
if (_tcsstr(pszLoader, _T("explorer.exe")))
return FALSE;
}
一旦知道了當(dāng)前進(jìn)程是Windows資源管理器,可立即退出。
注意,再多加一些條件語句是危險(xiǎn)的!事實(shí)上,另外一些進(jìn)程試圖裝入該DLL時(shí)將被放棄。如果你做另外一個(gè)試驗(yàn),比方說針對(duì)Internet Explorer的執(zhí)行文件iexplorer.exe,這時(shí)第一個(gè)受害者就是regsvr32.exe(該程序用于自動(dòng)注冊(cè)對(duì)象)。
if (!_tcsstr(pszLoader, _T("iexplore.exe")))
你不能夠再次注冊(cè)該DLL庫了。 事實(shí)上,當(dāng) regsvr32.exe 試圖裝入DLL以激活函數(shù)DllRegisterServer()時(shí),該調(diào)用將被放棄。

八、與Web瀏覽器取得聯(lián)系
SetSite()方法正是BHO對(duì)象被初始化的地方,此外,在這個(gè)方法中你可以執(zhí)行所有的僅僅允許發(fā)生一次的任務(wù)。當(dāng)你用Internet Explorer打開一個(gè)URL時(shí),你應(yīng)該等待一系列的事件以確保要求的文檔已完全下載并被初始化。唯有在此時(shí),你才可以通過對(duì)象模型暴露的接口(如果存 在的話)存取文檔內(nèi)容。這就是說你要取得一系列的指針。第一個(gè)就是指向IWebBrowser2(該接口用來生成WebBrowser對(duì)象)的指針。第二 個(gè)指針與事件有關(guān)。該模塊必須作為一個(gè)瀏覽器的事件偵聽器來實(shí)現(xiàn),目的是為接收下載以及與文檔相關(guān)的事件。下面用ATL靈敏指針加以封裝:
CComQIPtr< IWebBrowser2, &IID_IWebBrowser2> m_spWebBrowser2;
CComQIPtr<IConnectionPointContainer,
&IID_IConnectionPointContainer> m_spCPC;
源代碼部分如下所示:
HRESULT CViewSource::SetSite(IUnknown *pUnkSite)
{
// 檢索并存儲(chǔ) IWebBrowser2 指針
m_spWebBrowser2 = pUnkSite;
if (m_spWebBrowser2 == NULL)
return E_INVALIDARG;
//檢索并存儲(chǔ) IConnectionPointerContainer指針
m_spCPC = m_spWebBrowser2;
if (m_spCPC == NULL)
return E_POINTER;
//檢索并存儲(chǔ)瀏覽器的句柄HWND. 并且安裝一個(gè)鍵盤鉤子備后用
RetrieveBrowserWindow();
// 為接受事件通知連接到容器
return Connect();
}
為了取得IWebBrowser2接口指針,你可以進(jìn)行查詢。當(dāng)然也可以在事件剛剛發(fā)生時(shí)查詢IConnectionPointContainer。 這里,SetSite()檢索了瀏覽器的句柄HWND,并且在當(dāng)前線程中安裝了一個(gè)鍵盤鉤子。HWND用于后面Internet Explorer窗口的移動(dòng)或尺寸調(diào)整。這里的鉤子用來實(shí)現(xiàn)熱鍵功能,用戶可以按動(dòng)熱鍵來顯示/隱藏代碼窗口。

九、從Internet Explorer瀏覽器取得事件
當(dāng)你導(dǎo)向一個(gè)新的URL時(shí),瀏覽器最需要完成的是兩種事件:下載文檔并為之準(zhǔn)備HOST環(huán)境。也就是說,它必須初始化某對(duì)象并使該對(duì)象從外部可以利 用。針對(duì)不同的文檔類型,或者裝入一個(gè)已注冊(cè)的Microsoft ActiveX? 服務(wù)器來處理該文檔(如Word對(duì)于.doc文件的處理)或者初始化一些內(nèi)部組件來分析文檔內(nèi)容并生成和顯示該文檔。對(duì)于HTML網(wǎng)頁就是這樣,其內(nèi)容由 于DHTML對(duì)象作用而變得可用。當(dāng)文檔全部下載結(jié)束,DownloadComplete事件被激活。這并不是說,這樣利用對(duì)象模型就可以安全地管理文檔 的內(nèi)容了。事實(shí)上,DocumentComplete 事件僅指明一切已經(jīng)結(jié)束,文檔已準(zhǔn)備好了 (注意DocumentComplete事件僅在你第一次存取URL時(shí)到達(dá),如果你執(zhí)行了刷新動(dòng)作,你僅僅收到一個(gè)DocumentComplete事 件)。
為了截獲瀏覽器發(fā)出的事件, BHO需要通過IConnectionPoint 接口連接到瀏覽器上 并且實(shí)現(xiàn)傳遞接口IDispatch指針以處理各種事件?,F(xiàn)在利用前面取得的IConnectionPointContainer指針來調(diào)用 FindConnectionPoint方法――它返回一個(gè)指針指向連接點(diǎn)對(duì)象(正是通過這個(gè)連接點(diǎn)對(duì)象來取得要求的外向接口,此時(shí)是 DIID_DWebBrowserEvent2)。 下列代碼顯示了連接點(diǎn)的發(fā)生情況:
HRESULT CViewSource::Connect(void)
{
HRESULT hr;
CComPtr<IConnectionPoint> spCP;
//為Web瀏覽器事件而接收(receive)連接點(diǎn)
hr = m_spCPC->FindConnectionPoint(DIID_DWebBrowserEvent2, &spCP);
if (FAILED(hr))
return hr;
// 把事件處理器傳遞到容器。每次事件發(fā)生容器都將激活我們實(shí)現(xiàn)的IDispatch接口上的相應(yīng)的函數(shù)。
hr = spCP->Advise( reinterpret_cast<IDispatch*>(this), &m_dwCookie);
return hr;
}
通過調(diào)用接口IConnectionPoint的Advise() 方法, BHO告訴瀏覽器它對(duì)它產(chǎn)生的事件很感興趣。 由于COM事件處理機(jī)制,所有這些意味著BHO把IDispatch接口指針提供給瀏覽器。瀏覽器將回調(diào)IDispatch接口的Invoke() 方法,以事件的ID值作為第一參數(shù):
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;
}
:
}
切記,當(dāng)事件不再需要時(shí),應(yīng)該使之與瀏覽器分離。如果你忘記了做這件事情,BHO對(duì)象將被鎖定,即使在你關(guān)閉瀏覽器窗口之后。很明顯,實(shí)現(xiàn)分離的最佳時(shí)機(jī)是收到事件OnQuit時(shí)。

十、存取文檔對(duì)象
此時(shí),該BHO已經(jīng)有一個(gè)參照指向Internet Explorer的Web瀏覽器控件并被連接到瀏覽器控件以接收所有它產(chǎn)生的事件。當(dāng)網(wǎng)頁被全部下載并正確初始化后,我們就可以通過DHTML文檔模型存取它。Web瀏覽器的文檔屬性返回一個(gè)指向文檔對(duì)象的IDispatch接口的指針:
CComPtr<IDispatch> pDisp;
HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);
get_Document() 方法取得的僅僅是一個(gè)接口指針。我們要進(jìn)一步確定在IDispatch 指針背后存在一個(gè)HTML文檔對(duì)象。用VB實(shí)現(xiàn)的話,可以用下面代碼:
Dim doc As Object
Set doc = WebBrowser1.Document
If TypeName(doc)="HTMLDocument" Then
'' 獲取文檔內(nèi)容并予以顯示
Else
'' Disable the display dialog
End If
現(xiàn)在要了解一下get_Document()返回的IDispatch指針 。Internet Explorer不僅僅是一個(gè)HTML瀏覽器,而且還是一個(gè)ActiveX文檔容器。 這樣一來,難以保證當(dāng)前瀏覽對(duì)象就是一個(gè)HTML文檔。不過辦法還是有的――你想,如果IDispatch指針真正指向一個(gè)HTML文檔,查詢IHTMLDocument2 接口一定成功。
IHTMLDocument2接口包裝了DHTML對(duì)象模型用來展現(xiàn)HTML頁面的所有功能。下面代碼實(shí)現(xiàn)這些功能:
CComPtr<IDispatch> pDisp;
HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);
CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> spHTML;
spHTML = pDisp;
if (spHTML) {
// 獲取文檔內(nèi)容并予以顯示
}
else {
// disable the Code Window controls
}
如果IHTMLDocument2接口查詢失敗,spHTML指針將是NULL。
現(xiàn)在考慮如何獲得當(dāng)前顯示窗口的源代碼。正如一個(gè)HTML頁把它所有的內(nèi)容封裝在標(biāo)簽<BODY>中,DHTML對(duì)象模型要求你取得一個(gè)指向Body對(duì)象的指針:
CComPtr<IHTMLElement> m_pBody;
hr = spHTML->get_body(&m_pBody);
奇怪的是,DHTML對(duì)象模型不讓你取得標(biāo)簽<BODY>之前的原始內(nèi)容,如<HEAD>。其內(nèi)容被處理并存于一些屬性中,但你 還是不能從HTML原始文件中提取這部分的RAW文本。這過,僅從BODY部分取得的內(nèi)容足夠了。為了取得包含 在<BODY>…</BODY>間的HTML代碼部分,可以把outerHTML屬性內(nèi)容讀取到一個(gè)BSTR變量中:
BSTR bstrHTMLText;
hr = m_pBody->get_outerHTML(&bstrHTMLText);
在此基礎(chǔ)上,在代碼窗口中顯示源碼就是一種簡單的事情了:生成一個(gè)窗口,進(jìn)行字符的UNICODE至ANSI轉(zhuǎn)化和設(shè)置編輯框控件的問題。下面代碼實(shí)現(xiàn)這些功能:
HRESULT CViewSource::GetDocumentContent()
{
USES_CONVERSION;
// 獲取 WebBrowser的文檔對(duì)象
CComPtr<IDispatch> pDisp;
HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);
if (FAILED(hr))
return hr;
// 確保我們?nèi)〉玫氖且粋€(gè)IHTMLDocument2接口指針
//讓我們查詢一下 IHTMLDocument2 接口 (使用靈敏指針)
CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> spHTML;
spHTML = pDisp;
// 抽取文檔源代碼
if (spHTML)
{
// 取得BODY 對(duì)象
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;
// 進(jìn)行文本的Unicode到 ANSI的轉(zhuǎn)換
LPTSTR psz = new TCHAR[SysStringLen(bstrHTMLText)];
lstrcpy(psz, OLE2T(bstrHTMLText));
// 文本進(jìn)行相應(yīng)的調(diào)整
HWND hwnd = m_dlgCode.GetDlgItem(IDC_TEXT);
EnableWindow(hwnd, true);
hwnd = m_dlgCode.GetDlgItem(IDC_APPLY);
EnableWindow(hwnd, true);
// 設(shè)置代碼窗口中的文本
m_dlgCode.SetDlgItemText(IDC_TEXT, psz);
delete [] psz;
}
else   // 文檔不是一個(gè) 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;
}
因?yàn)槲乙\(yùn)行這段代碼來響應(yīng)DocumentComplete事件通知,每個(gè)新的頁自動(dòng)地而且敏捷地被處理。DHTML對(duì)象模型使你能夠隨意修改網(wǎng)頁 的結(jié)構(gòu),但這一變化在按F5刷新后全部復(fù)原。你還要處理一下DownloadComplete事件以刷新代碼窗口 (注意, DownloadComplete 事件發(fā)生在 DocumentComplete事件之前)。你應(yīng)該忽略網(wǎng)頁的首次DownloadComplete事件,而是在執(zhí)行刷新動(dòng)作時(shí)才關(guān)注這一事件。布爾成 員變量m_bDocumentCompleted正是用來區(qū)別這兩種情形的。

十一、管理代碼窗口
用來顯示當(dāng)前HTML頁原始碼的代碼窗口涉及另外一個(gè)ATL 基本編程問題-對(duì)話框窗口,它位于ATL對(duì)象向?qū)У?Miscellaneous"選項(xiàng)卡下。
我調(diào)整了代碼窗口的大小來響應(yīng)WM_INITDIALOG消息,使它占居桌面空間的下部區(qū)域,正好是在任務(wù)欄的上面。在瀏覽器啟動(dòng)時(shí)你可以選擇顯示或 不顯示這個(gè)窗口。缺省情況下是顯示的,但這可以通過清除"Show window at startup"復(fù)選框項(xiàng)來實(shí)現(xiàn)。當(dāng)然喜歡的話,你可以隨時(shí)關(guān)閉。按鍵F12即可重新顯示代碼窗口。F12是通過在SetSite()中安裝的鍵盤鉤子實(shí) 現(xiàn)的。啟動(dòng)環(huán)境存于WINDOWS注冊(cè)表中,我選擇外殼庫文件shlwapi.dll中函數(shù)SHGetValue來實(shí)現(xiàn)注冊(cè)表的讀寫操作。這同使用Reg 開頭的Win32函數(shù)操作相比,簡單極了。請(qǐng)看:
DWORD dwType, dwVal;
DWORD dwSize = sizeof(DWORD);
SHGetValue(HKEY_CURRENT_USER, _T("Software\\MSDN\\BHO"), _T("ShowWindowAtStartup"), &dwType, &dwVal, &dwSize);
這個(gè)DLL文件是同Internet Explorer 4.0 和活動(dòng)桌面的誕生一起產(chǎn)生的,是WIN98及以后版本的標(biāo)準(zhǔn)組成,你可以放心使用。

十二、注冊(cè)BHO對(duì)象
因?yàn)锽HO 是一個(gè)COM 服務(wù)器,所以既應(yīng)該作為COM 服務(wù)器注冊(cè)又應(yīng)該作為BHO對(duì)象注冊(cè)。ATL向?qū)ё詣?dòng)生成.rgs文件,第一種情況的注冊(cè)就免除了。下面的文件代碼段是用來實(shí)現(xiàn)作為BHO對(duì)象注冊(cè)的(CLSID為例中生成)。
HKLM {
SOFTWARE {
Microsoft {
Windows {
CurrentVersion {
Explorer {
''BHO'' {
ForceRemove {1E1B2879-88FF-11D2-8D96-D7ACAC95951F}
}}}}}}}
注意ForceRemove一詞能夠?qū)崿F(xiàn)在卸載對(duì)象時(shí)刪除這一行相應(yīng)的鍵值。BHO鍵下聚集了所有的BHO對(duì)象。對(duì)于這么多的一串家伙是從來不作緩沖調(diào)用的。這樣以來,安裝與測(cè)試BHO就是不費(fèi)時(shí)的事情了。

十三、總結(jié)
本文描述了BHO對(duì)象,通過它你可以把自己的代碼注入瀏覽器的地址空間中。你必須做的事情是寫一個(gè)支持IObjectWithSite 接口的COM 服務(wù)器。在這一點(diǎn)上,你的BHO對(duì)象可以實(shí)現(xiàn)瀏覽器機(jī)制范圍內(nèi)的各種合法目的。本文所及示例涉及了COM事件,DHTML對(duì)象模型以及WEB瀏覽器編程接 口。雖然內(nèi)容稍寬一些,但它正顯示了現(xiàn)實(shí)世界中的BHO對(duì)象的應(yīng)用。如,你想知道瀏覽器在顯示什么,那么您就需要了解接收事件并要熟悉WEB瀏覽器才行。
另外:Windows資源管理器也是與BHO對(duì)象交互的,這一點(diǎn)在編程時(shí)要特別注意。本文所附源程序?yàn)镸SDN所帶,在Windows2000/VC6下調(diào)試通過(編譯通過后,重新啟動(dòng)IE即得到結(jié)果)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多