如何使用BHO定制你的Internet Explorer瀏覽器 原文出處:Browser
Helper Objects: The Browser the Way You Want It
如果你對SHELL擴展編程有興趣的話,可以參考MSDN有關(guān)資料。
BHO對象隨著瀏覽器主窗口的顯示而裝入,隨著瀏覽器主窗口的銷毀而缷載。如果你打開多個瀏覽器窗口,多個BHO實例也一同產(chǎn)生。
對BHO 的唯一嚴格的要求正在于必須實現(xiàn)這一個接口。 注意你應該避免在調(diào)用以上任何一個函數(shù)時返回E_NOTIMPL 。
要么你不實現(xiàn)這一接口,要么應保證在調(diào)用這些方法時進行正確地編碼。 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繼承,這是一個ATL模板類,它提供了接口IObjectWithSite的基本實現(xiàn)。一般情況下,沒有必要重載成員函數(shù)GetSite()。取而代之的是, SetSite() 實現(xiàn)代碼經(jīng)常需要加以定制。ATL實際上僅僅把一個IUnknown接口指針存儲在成員變量m_spUnkSite中。 在文章的剩余部分,我將討論一個 BHO 的相當復雜而豐富的例子。該BHO對象將依附于Internet Explorer,并顯示一個文本框來顯示當前正瀏覽的網(wǎng)頁源碼。 該代碼窗口將 隨著你改變網(wǎng)頁而自動更新,如果瀏覽器顯示的不是一個HTML網(wǎng)頁時,它將變灰。你對于原始HTML代碼的任何改動立即反映在瀏覽器中。HTML (DHTML)使得這一看似魔術(shù)般的實現(xiàn)成為可能。該代碼窗口可被隱藏和通過按動熱鍵重現(xiàn)。 在可見情況下,它與Internet Explorer共享整個桌面空間,見圖三。 ![]() 圖三 BHO對象在使用中。它依附于Internet Explorer,并顯示一個窗口來顯示當前正瀏覽的網(wǎng)頁源碼。還允許你源碼進行修改。 本例子的關(guān)鍵點在于存取Internet Explorer的瀏覽機制,其實它只不過是WebBrowser控件的一個實例而已。這個例子可以分解為以下五步來實現(xiàn):
第一個步驟是在DllMain()中完成的。SetSite()是取得指向WebBrowser對象指針的適當位置。請詳細分析以下步驟。 if (dwReason == DLL_PROCESS_ATTACH) { TCHAR pszLoader[MAX_PATH]; //返回調(diào)用者模塊的名稱,第一個參數(shù)應為NULL,詳見msdn。 GetModuleFileName(NULL, pszLoader, MAX_PATH); _tcslwr(pszLoader); if (_tcsstr(pszLoader, _T("explorer.exe"))) return FALSE; }一旦知道了當前進程是Windows資源管理器,可立即退出。 注意,再多加一些條件語句是危險的!事實上,另外一些進程試圖裝入該DLL時將被放棄。如果你做另外一個試驗,比方說針對Internet Explorer的執(zhí)行文件iexplorer.exe,這時第一個受害者就是regsvr32.exe(該程序用于自動注冊對象)。 if (!_tcsstr(pszLoader, _T("iexplore.exe")))你不能夠再次注冊該DLL庫了。 事實上,當 regsvr32.exe 試圖裝入DLL以激活函數(shù)DllRegisterServer()時,該調(diào)用將被放棄。 八、與Web瀏覽器取得聯(lián)系 SetSite()方法正是BHO對象被初始化的地方,此外,在這個方法中你可以執(zhí)行所有的僅僅允許發(fā)生一次的任務。當你用Internet Explorer打開一個URL時,你應該等待一系列的事件以確保要求的文檔已完全下載并被初始化。唯有在此時,你才可以通過對象模型暴露的接口(如果存 在的話)存取文檔內(nèi)容。這就是說你要取得一系列的指針。第一個就是指向IWebBrowser2(該接口用來生成WebBrowser對象)的指針。第二 個指針與事件有關(guān)。該模塊必須作為一個瀏覽器的事件偵聽器來實現(xiàn),目的是為接收下載以及與文檔相關(guān)的事件。下面用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接口指針,你可以進行查詢。當然也可以在事件剛剛發(fā)生時查詢IConnectionPointContainer。 這里,SetSite()檢索了瀏覽器的句柄HWND,并且在當前線程中安裝了一個鍵盤鉤子。HWND用于后面Internet Explorer窗口的移動或尺寸調(diào)整。這里的鉤子用來實現(xiàn)熱鍵功能,用戶可以按動熱鍵來顯示/隱藏代碼窗口。 九、從Internet Explorer瀏覽器取得事件 當你導向一個新的URL時,瀏覽器最需要完成的是兩種事件:下載文檔并為之準備HOST環(huán)境。也就是說,它必須初始化某對象并使該對象從外部可以利 用。針對不同的文檔類型,或者裝入一個已注冊的Microsoft ActiveX? 服務器來處理該文檔(如Word對于.doc文件的處理)或者初始化一些內(nèi)部組件來分析文檔內(nèi)容并生成和顯示該文檔。對于HTML網(wǎng)頁就是這樣,其內(nèi)容由 于DHTML對象作用而變得可用。當文檔全部下載結(jié)束,DownloadComplete事件被激活。這并不是說,這樣利用對象模型就可以安全地管理文檔 的內(nèi)容了。事實上,DocumentComplete 事件僅指明一切已經(jīng)結(jié)束,文檔已準備好了 (注意DocumentComplete事件僅在你第一次存取URL時到達,如果你執(zhí)行了刷新動作,你僅僅收到一個DocumentComplete事 件)。 為了截獲瀏覽器發(fā)出的事件, BHO需要通過IConnectionPoint 接口連接到瀏覽器上 并且實現(xiàn)傳遞接口IDispatch指針以處理各種事件?,F(xiàn)在利用前面取得的IConnectionPointContainer指針來調(diào)用 FindConnectionPoint方法――它返回一個指針指向連接點對象(正是通過這個連接點對象來取得要求的外向接口,此時是 DIID_DWebBrowserEvent2)。 下列代碼顯示了連接點的發(fā)生情況: HRESULT CViewSource::Connect(void) { HRESULT hr; CComPtr<IConnectionPoint> spCP; //為Web瀏覽器事件而接收(receive)連接點 hr = m_spCPC->FindConnectionPoint(DIID_DWebBrowserEvent2, &spCP); if (FAILED(hr)) return hr; // 把事件處理器傳遞到容器。每次事件發(fā)生容器都將激活我們實現(xiàn)的IDispatch接口上的相應的函數(shù)。 hr = spCP->Advise( reinterpret_cast<IDispatch*>(this), &m_dwCookie); return hr; }通過調(diào)用接口IConnectionPoint的Advise() 方法, BHO告訴瀏覽器它對它產(chǎn)生的事件很感興趣。 由于COM事件處理機制,所有這些意味著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; } : }切記,當事件不再需要時,應該使之與瀏覽器分離。如果你忘記了做這件事情,BHO對象將被鎖定,即使在你關(guān)閉瀏覽器窗口之后。很明顯,實現(xiàn)分離的最佳時機是收到事件OnQuit時。 十、存取文檔對象 此時,該BHO已經(jīng)有一個參照指向Internet Explorer的Web瀏覽器控件并被連接到瀏覽器控件以接收所有它產(chǎn)生的事件。當網(wǎng)頁被全部下載并正確初始化后,我們就可以通過DHTML文檔模型存取它。Web瀏覽器的文檔屬性返回一個指向文檔對象的IDispatch接口的指針: CComPtr<IDispatch> pDisp; HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);get_Document() 方法取得的僅僅是一個接口指針。我們要進一步確定在IDispatch 指針背后存在一個HTML文檔對象。用VB實現(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不僅僅是一個HTML瀏覽器,而且還是一個ActiveX文檔容器。 這樣一來,難以保證當前瀏覽對象就是一個HTML文檔。不過辦法還是有的――你想,如果IDispatch指針真正指向一個HTML文檔,查詢IHTMLDocument2 接口一定成功。 IHTMLDocument2接口包裝了DHTML對象模型用來展現(xiàn)HTML頁面的所有功能。下面代碼實現(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)在考慮如何獲得當前顯示窗口的源代碼。正如一個HTML頁把它所有的內(nèi)容封裝在標簽<BODY>中,DHTML對象模型要求你取得一個指向Body對象的指針: CComPtr<IHTMLElement> m_pBody; hr = spHTML->get_body(&m_pBody);奇怪的是,DHTML對象模型不讓你取得標簽<BODY>之前的原始內(nèi)容,如<HEAD>。其內(nèi)容被處理并存于一些屬性中,但你 還是不能從HTML原始文件中提取這部分的RAW文本。這過,僅從BODY部分取得的內(nèi)容足夠了。為了取得包含 在<BODY>…</BODY>間的HTML代碼部分,可以把outerHTML屬性內(nèi)容讀取到一個BSTR變量中: BSTR bstrHTMLText; hr = m_pBody->get_outerHTML(&bstrHTMLText);在此基礎(chǔ)上,在代碼窗口中顯示源碼就是一種簡單的事情了:生成一個窗口,進行字符的UNICODE至ANSI轉(zhuǎn)化和設(shè)置編輯框控件的問題。下面代碼實現(xiàn)這些功能: HRESULT CViewSource::GetDocumentContent() { USES_CONVERSION; // 獲取 WebBrowser的文檔對象 CComPtr<IDispatch> pDisp; HRESULT hr = m_spWebBrowser2->get_Document(&pDisp); if (FAILED(hr)) return hr; // 確保我們?nèi)〉玫氖且粋€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的轉(zhuǎn)換 LPTSTR psz = new TCHAR[SysStringLen(bstrHTMLText)]; lstrcpy(psz, OLE2T(bstrHTMLText)); // 文本進行相應的調(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 // 文檔不是一個 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對象模型使你能夠隨意修改網(wǎng)頁 的結(jié)構(gòu),但這一變化在按F5刷新后全部復原。你還要處理一下DownloadComplete事件以刷新代碼窗口 (注意, DownloadComplete 事件發(fā)生在 DocumentComplete事件之前)。你應該忽略網(wǎng)頁的首次DownloadComplete事件,而是在執(zhí)行刷新動作時才關(guān)注這一事件。布爾成 員變量m_bDocumentCompleted正是用來區(qū)別這兩種情形的。 十一、管理代碼窗口 用來顯示當前HTML頁原始碼的代碼窗口涉及另外一個ATL 基本編程問題-對話框窗口,它位于ATL對象向?qū)У?Miscellaneous"選項卡下。 我調(diào)整了代碼窗口的大小來響應WM_INITDIALOG消息,使它占居桌面空間的下部區(qū)域,正好是在任務欄的上面。在瀏覽器啟動時你可以選擇顯示或 不顯示這個窗口。缺省情況下是顯示的,但這可以通過清除"Show window at startup"復選框項來實現(xiàn)。當然喜歡的話,你可以隨時關(guān)閉。按鍵F12即可重新顯示代碼窗口。F12是通過在SetSite()中安裝的鍵盤鉤子實 現(xiàn)的。啟動環(huán)境存于WINDOWS注冊表中,我選擇外殼庫文件shlwapi.dll中函數(shù)SHGetValue來實現(xiàn)注冊表的讀寫操作。這同使用Reg 開頭的Win32函數(shù)操作相比,簡單極了。請看: 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 和活動桌面的誕生一起產(chǎn)生的,是WIN98及以后版本的標準組成,你可以放心使用。 十二、注冊BHO對象 因為BHO 是一個COM 服務器,所以既應該作為COM 服務器注冊又應該作為BHO對象注冊。ATL向?qū)ё詣由?rgs文件,第一種情況的注冊就免除了。下面的文件代碼段是用來實現(xiàn)作為BHO對象注冊的(CLSID為例中生成)。 HKLM { SOFTWARE { Microsoft { Windows { CurrentVersion { Explorer { ''BHO'' { ForceRemove {1E1B2879-88FF-11D2-8D96-D7ACAC95951F} }}}}}}}注意ForceRemove一詞能夠?qū)崿F(xiàn)在卸載對象時刪除這一行相應的鍵值。BHO鍵下聚集了所有的BHO對象。對于這么多的一串家伙是從來不作緩沖調(diào)用的。這樣以來,安裝與測試BHO就是不費時的事情了。 十三、總結(jié) 本文描述了BHO對象,通過它你可以把自己的代碼注入瀏覽器的地址空間中。你必須做的事情是寫一個支持IObjectWithSite 接口的COM 服務器。在這一點上,你的BHO對象可以實現(xiàn)瀏覽器機制范圍內(nèi)的各種合法目的。本文所及示例涉及了COM事件,DHTML對象模型以及WEB瀏覽器編程接 口。雖然內(nèi)容稍寬一些,但它正顯示了現(xiàn)實世界中的BHO對象的應用。如,你想知道瀏覽器在顯示什么,那么您就需要了解接收事件并要熟悉WEB瀏覽器才行。 另外:Windows資源管理器也是與BHO對象交互的,這一點在編程時要特別注意。本文所附源程序為MSDN所帶,在Windows2000/VC6下調(diào)試通過(編譯通過后,重新啟動IE即得到結(jié)果)。 |
|
來自: xiaoqdu > 《開發(fā)資料》