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