有時,你可能需要一個定制版本的浏覽器,或者開發一款RPA流程機器人ie浏覽器的拾取器(通過開發IE插件來拾取IE浏覽器相關控件元素)。在這種情況下,你可以自由地把一些新穎但又不标準的特征增加到一個浏覽器上。結果,你最終有的隻是一個新但不标準的浏覽器。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控件的一個實例而已。這個例子可以分解為以下五步來實現:
第一個步驟是在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 Object
Set doc = WebBrowser1.Document
If TypeName(doc)="HTMLDocument" Then
'' 獲取文檔内容并予以顯示
Else
'' Disable the display dialog
End 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即得到結果)。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!