如何使用BHO定制你的Internet Explorer瀏覽器 原文出處:Browser
Helper Objects: The Browser the Way You Want It
如果你對(duì)SHELL擴(kuò)展編程有興趣的話,可以參考MSDN有關(guān)資料。
BHO對(duì)象隨著瀏覽器主窗口的顯示而裝入,隨著瀏覽器主窗口的銷毀而缷載。如果你打開多個(gè)瀏覽器窗口,多個(gè)BHO實(shí)例也一同產(chǎ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)行正確地編碼。 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):
第一個(gè)步驟是在DllMain()中完成的。SetSite()是取得指向WebBrowser對(duì)象指針的適當(dāng)位置。請(qǐng)?jiān)敿?xì)分析以下步驟。 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é)果)。 |
|