• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            勤能補拙,Expter

            成都游戲Coder,記錄游戲開發(fā)過程的筆記和心得!

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

            原文出處:Browser Helper Objects: The Browser the Way You Want It
            一、簡介
              有時,你可能需要一個定制版本的瀏覽器。在這種情況下,你可以自由地把一些新穎但又不標(biāo)準(zhǔn)的特征增加到一個瀏覽器上。結(jié)果,你最終有的只是一個新但不標(biāo)準(zhǔn)的瀏覽器。Web瀏覽器控件只是瀏覽器的分析引擎。這意味著仍然存在若干的與用戶接口相關(guān)的工作等待你做――增加一個地址欄,工具欄,歷史記錄,狀態(tài)欄,頻道欄和收藏夾等。如此,要產(chǎn)生一個定制的瀏覽器,你可以進(jìn)行兩種類型的編程――一種象微軟把Web瀏覽器控件轉(zhuǎn)變成一個功能齊全的瀏覽器如Internet Explorer;一種是在現(xiàn)有的基礎(chǔ)上加一些新的功能。如果有一個直接的方法定制現(xiàn)有的Internet Explorer該多好?BHO(Browser Helper Objects,我譯為"瀏覽器幫助者對象",以下皆簡稱BHO)正是用來實現(xiàn)此目的的。
            二、關(guān)于軟件定制
              以前,定制一個軟件的行為主要是通過子類化方法實現(xiàn)的。 通過這種辦法,你可以改變一個窗口的外表與行為。子類化雖然被認(rèn)為是一種有點暴力方式――受害者根本不知道發(fā)生的事情――但它還是長時間以來的唯一的選擇。
              隨著微軟Win32 API的到來,進(jìn)程間子類化不再被鼓勵使用并愈發(fā)變得困難起來。當(dāng)然,如果你是勇敢的--指針從未嚇倒你,而最重要的是,如果你已經(jīng)游刃于系統(tǒng)鉤子之間,你可能覺得這一問題太簡單了。 但是情形并不總是這樣。暫放下這點不管,問題在于每一個進(jìn)程運行在自己的地址空間中,而且打破進(jìn)程邊界略微有些不正確性。 另一方面, 你可能需要對定制進(jìn)行更好的管理。更經(jīng)常情況下,定制可能是程序本身強烈要求實現(xiàn)的。
              在后者情況下,已安裝的軟件只需在既定的磁盤位置查詢另外的組件模塊,然后裝載、設(shè)定初值,最后讓它們自由地按照既定的設(shè)計工作。這正是Internet Explorer瀏覽器和它的BHO所要實現(xiàn)的。
            三、什么是BHO?
              從某種觀點看,Internet Explorer同普通的Win32程序沒有什么兩樣。借助于BHO,你可以寫一個進(jìn)程內(nèi)COM對象,這個對象在每次啟動時都要加載。這樣的對象會在與瀏覽器相同的上下文中運行,并能對可用的窗口和模塊執(zhí)行任何行動。例如,一個BHO能夠探測到典型的事件,如GoBack、GoForward、DocumentComplete等;另外BHO能夠存取瀏覽器的菜單與工具欄并能做出修改,還能夠產(chǎn)生新窗口來顯示當(dāng)前網(wǎng)頁的一些額外信息,還能夠安裝鉤子以監(jiān)控一些消息和動作。簡而言之, BHO的工作如我們打入瀏覽器領(lǐng)地的一位間諜(注意這是微軟允許的合法工作)。
              在進(jìn)一步了解BHO細(xì)節(jié)之前,有幾點我需要進(jìn)一步闡述。首先,BHO對象依托于瀏覽器主窗口。實際上,這意味著一旦一個瀏覽器窗口產(chǎn)生,一個新的BHO對象實例就要生成。任何 BHO對象與瀏覽器實例的生命周期是一致的。其次, BHO僅存在于Internet Explorer 4.0及以后版本中。
            如果你在使用Microsoft Windows? 98, Windows 2000, Windows 95, or Windows NT版本4.0 操作系統(tǒng)的話,也就一塊運行了活動桌面外殼4.71,BHO也被 Windows資源管理器所支持。 BHO是一個COM進(jìn)程內(nèi)服務(wù),注冊于注冊表中某一鍵下。在啟動時,Internet Explorer查詢那個鍵并把該鍵下的所有對象預(yù)以加載。
              Internet Explorer瀏覽器初始化這一對象并要求某一接口功能。如果發(fā)現(xiàn)這一接口, Internet Explorer使用其提供的方法傳遞 IUnknown 指針到BHO對象。見圖一:

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

            特性
            外殼擴展
            BHO對象

            加載者
            Windows資源管理器
            Internet Explorer(和外殼4.17及以上版本的Windows資源管理器)

            擊活動作
            在某類文檔上的用戶動作(即單擊右鍵)
            打開瀏覽器窗口

            何時卸載
            參考計數(shù)達(dá)到0的幾秒之后
            導(dǎo)致它加載的窗口關(guān)閉時

            實現(xiàn)形式
            COM進(jìn)程中DLL
            COM 進(jìn)程中 DLL

            注冊需求
            常常是為一個COM服務(wù)器設(shè)置的入口處,另加的入口依賴于外殼類型及它要應(yīng)用至的文檔類型
            常常是為一個COM服務(wù)器設(shè)置的入口處,另加一個把它申請為BHO的注冊入口

            接口需求
            依賴于外殼擴展的類型
            IObjectWithSite

            如果你對SHELL擴展編程有興趣的話,可以參考MSDN有關(guān)資料。
            四、BHO的生存周期
              前面已經(jīng)說過,BHO不僅僅為Internet Explorer所支持。如果你在使用外殼 4.71或者更高版本,你的BHO對象也會被Windows資源管理器所加載。下表二展示了我們可以使用的不同版本的外殼產(chǎn)品情況,Windows外殼版本號存于庫文件shell32.dll中。
            表二 不同版本的Windows外殼對于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 和活動桌面外殼更新
            Internet Explorer 與Windows 資源管理器

            4.72
            Windows 98
            Internet Explorer與Windows 資源管理器

            5.00
            Windows 2000
            Internet Explorer與Windows 資源管理器

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

            圖二 經(jīng)過這樣設(shè)置,你每次打開一個文件夾時,執(zhí)行一個獨立的explorer.exe實例,并裝入已注冊的BHO對象。
              但是注意,這種情形僅適于當(dāng)你從桌面上的"我的電腦"圖標(biāo)中打開文件夾的情況。在這種情況下,每次你移到另外一個文件夾時外殼都要調(diào)用explorer.exe。這種情況在你同時用兩個窗格進(jìn)行瀏覽時是不會發(fā)生的。事實上,當(dāng)你改變文件夾時,外殼是不會啟動瀏覽器的新的實例的而僅是簡單創(chuàng)建嵌入視圖對象的另外一個實例。奇怪的是,如果你在地址欄中輸入一個新的名字來改變文件夾時,在同一個窗口中同樣可以達(dá)到瀏覽之目的,無論Windows資源管理器視圖是單個的還是雙視圖形式。
              對于Internet Explorer的情形,事情要更簡單一些。只有你顯式地多次運行iexplore.exe瀏覽器時,你才有多個Internet Explorer的拷貝。當(dāng)你從Internet Explorer中打開新的窗口時,每一個窗口在一個新的線程中被復(fù)制而不是創(chuàng)建一個新的進(jìn)程,因此也就不需要重新載入BHO對象。
              首先,BHO最有趣的地方是,它是極度動態(tài)的。每次Windows資源管理器或者Internet Explorer打開,裝載器從注冊表中讀取已安裝的BHO對象的CLSID然后處理它們。如果你在打開的瀏覽器多個實例中間編輯注冊表的話,你可以隨著多個瀏覽器拷貝的載入而裝入多個不同的BHO。 這就是說,如果你選擇從頭創(chuàng)建一個新的屬于自己的瀏覽器,那么你可以把它內(nèi)嵌在一個Visual Basic或者M(jìn)FC框架窗口中。同時你有相當(dāng)?shù)臋C會來靈活安排瀏覽程序。如果它們能滿足你的需要的話,你可以依賴于Internet Explorer的強大的功能并且加上你想要的盡可能多的插件。
            五、關(guān)于IObjectWithSite接口
              從一個高起點來看,BHO即是一個DLL,它能夠依附于Internet Explorer瀏覽器的一個新建的實例,在某些情況下也適用于Windows資源管理器。
              一般地,一個場所(site)是一個中間對象,它位于容器對象與被包容對象之間。通過它,容器對象管理被包容對象的內(nèi)容,也因此使得對象的內(nèi)部功能可用。為此,容器方要實現(xiàn)接口IoleClientSite,被包容對象要實現(xiàn)接口IOleObject 。通過調(diào)用IOleObject提供的方法,容器對象使得被包容對象清楚地了解其HOST的環(huán)境。
              一旦容器對象成為Internet Explorer(或是具有WEB能力的Windows資源管理器),被包容對象只需實現(xiàn)一個輕型的IObjectWithSite接口。該接口提供了以下方法:
            表三 IObjectWithSite定義

            方法
            描述

            HRESULT SetSite(IUnknown* pUnkSite)
            接收ie瀏覽器的IUnknown指針。典型實現(xiàn)是保存該指針以備將來使用。.

            HRESULT GetSite(REFIID riid, void** ppvSite)
            從通過SetSite()方法設(shè)置的場所中接收并返回指定的接口,典型實現(xiàn)是查詢前面保存的接口指針以進(jìn)一步取得指定的接口。

              對BHO 的唯一嚴(yán)格的要求正在于必須實現(xiàn)這一個接口。 注意你應(yīng)該避免在調(diào)用以上任何一個函數(shù)時返回E_NOTIMPL 。 要么你不實現(xiàn)這一接口,要么應(yīng)保證在調(diào)用這些方法時進(jìn)行正確地編碼。
            六、構(gòu)造自己的BHO對象
              一個BHO對象就是一個進(jìn)程中服務(wù)器DLL,選用ATL創(chuàng)建它是再恰當(dāng)不過的了。我們選擇ATL的另外一個原因是因為它已經(jīng)提供了缺省的而且提供了IObjectWithSite接口的足夠好的實現(xiàn)。另外,在ATL COM 向?qū)П镜刂С值囊讯x好的對象類型當(dāng)中,有一個,就是Internet Explorer對象,這正是一個BHO應(yīng)該具有的類型。一個 ATL Internet Explorer 對象,事實上是一個簡單對象――也就是說,是一個支持IUnknown和自注冊,還有接口IObjectWithSite的COM 服務(wù)器。如果你在ATL工程中添加一個這樣的對象,并調(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繼承,這是一個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的瀏覽機制,其實它只不過是WebBrowser控件的一個實例而已。這個例子可以分解為以下五步來實現(xiàn):
            1. 探測誰在裝入這個對象,是Internet Explorer還是Windows資源管理器;
            2. 獲取接口IWebBrowser2以實現(xiàn)Web瀏覽器對象;
            3. 捕捉Web瀏覽器的特定事件;
            4. 存取當(dāng)前文檔對象,確定它是一份HTML類型的文件;
            5. 管理對話框窗口以實現(xiàn)HTML源碼的顯示;

              第一個步驟是在DllMain()中完成的。SetSite()是取得指向WebBrowser對象指針的適當(dāng)位置。請詳細(xì)分析以下步驟。
            七、探測誰在調(diào)用這個對象
              如前所述,一個BHO對象會被Internet Explorer或者Windows資源管理器(前提:外殼版本4.71或者更高)所加載。所以我專門設(shè)計了一個BHO來處理HTML網(wǎng)頁,因此這個BHO與資源管理器毫無關(guān)系。如果一個Dll不想被調(diào)用者一起加載,只需在DllMain()中實現(xiàn)了探明誰在調(diào)用該對象后返回FALSE即可。參看下面代碼:

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

            posted on 2009-07-26 19:43 expter 閱讀(549) 評論(0)  編輯 收藏 引用


            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            久久99久久成人免费播放| 香蕉久久久久久狠狠色| 狠色狠色狠狠色综合久久| 一本一道久久精品综合| 久久久无码精品亚洲日韩软件| 免费一级做a爰片久久毛片潮 | 久久天天躁狠狠躁夜夜网站| 亚洲AV无码久久精品狠狠爱浪潮| 久久亚洲国产欧洲精品一| 亚洲AV伊人久久青青草原| 精品久久久久久国产潘金莲| 人妻精品久久久久中文字幕| 国产精品久久久久久影院| 中文字幕精品久久久久人妻| 久久99精品国产99久久6男男| 亚洲色欲久久久久综合网| 大美女久久久久久j久久| 免费精品久久天干天干| 91精品国产综合久久四虎久久无码一级 | 日韩AV毛片精品久久久| 久久99毛片免费观看不卡| 亚洲综合伊人久久大杳蕉| 香蕉久久永久视频| 一级做a爰片久久毛片16| www久久久天天com| 天天躁日日躁狠狠久久| 2021国产精品午夜久久| 97精品伊人久久久大香线蕉| 久久精品亚洲中文字幕无码麻豆 | 国产精品久久免费| 性欧美大战久久久久久久久| 香蕉久久夜色精品国产尤物| 欧美久久久久久午夜精品| 国产99久久九九精品无码| 久久ZYZ资源站无码中文动漫| 一本一道久久综合狠狠老| 国产精品99久久久久久宅男小说| 久久精品一区二区三区中文字幕| 国产69精品久久久久99| 久久精品无码av| 久久久久久久91精品免费观看|