c#開發IE控制項主要是對BHO對象是使用,但是我們知道BHO是一個COM對象,而在.NET下開發基於COM的應用,總覺得不是很簡單,這裡有受控代碼與COM的調用,我尋找了下,國內並沒有此類資訊,下面是譯稿,翻譯的不好,歡迎指出.
介紹:
我們在瀏覽Internet資訊的時候,往往需要增強使用者瀏覽資訊的,IE瀏覽器其實是一個可擴充的模型,提供了大量的外掛程式來完成這樣的目標.其實很多時候你已經在使用了,比如GOOGLE的工具條,Babelfish Translator,等你很多時候都在使用,這些外掛程式都是擴充BHO或瀏覽器中Band的對象,用這些外掛程式來整合一些複雜的功能,具體是實現一個COM的介面.
許多BHO對象的實現都是利用C+編寫一個ATL 組件,如果用c#來寫,即用受控代碼來實現COM 和 ATL ,好象是不可能實現的,但是很幸運,NET Framework提供了百分之百的與COM的互操作,在這篇文章裡,我介紹下怎麼利用.NET的互通性來實現一個BHO對象.
認識下COM:
我多年從事c++,MFC,VB,ATL的開發,所以,在.NET之前我想成為一個優秀的COM開發人員,正因為如此,COM的互通性一直在我腦海裡,雖然時間一長,我開發的都是完全用.NET開發的項目,COM變得很遙遠,但有的時候,
比如我想改寫一個ATL BHO 組件讓c#來使用,這個想法又付出水面.
在進一步瞭解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查詢那個鍵並把該鍵下的所有對象預以載入。
BHO對象隨著瀏覽器主視窗的顯示而裝入,隨著瀏覽器主視窗的銷毀而缷載。如果你開啟多個瀏覽器視窗,多個BHO執行個體也一同產生。
無論瀏覽器以什麼樣的命令列啟動,BHO對象都被載入。舉例來說,即使你只是想要見到特定的 HTML 頁或一個給定的檔案夾,BHO對象也被載入。一般地,當 explorer.exe 或 iexplore.exe 啟動並執行時候,BHO都要被考慮在內。如果你設定了"Open each folder in its own window"(對每一個檔案夾以一個獨立視窗開啟)檔案夾選項,那麼你每次開啟一個檔案夾,BHO對象都要被載入
如前所述,一個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時。