1.什么叫BHO
Browser Helper Objects,"瀏覽器幫助者對(duì)象",以下皆簡(jiǎn)稱BHO。
2.支持BHO特性的系統(tǒng)一覽表:
Shell版本 操作系統(tǒng)版本 支持BHO
4.00 Windows 95 and Windows NT 4.0(IE版本為 4.0) 僅IE4.0
4.71 Windows 95 and Windows NT 4.0(IE版本為 4.0) IE和文件瀏覽器
4.72 Windows 98 IE和文件瀏覽器
5.00 Windows 2000 IE和文件瀏覽器
3.BHO注冊(cè)表中的位置
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{7C260B4B-F7A0-40B5-B403-
BEFCDC6A4C3B}
IE啟動(dòng)時(shí)候根據(jù)此項(xiàng)到HKEY_CLASSES_ROOT\CLSID下面找到對(duì)應(yīng)BHO組件加載。
4.基本邏輯
IE將自己的IUnKnown指針傳遞給BHO,BHO需要建立一個(gè)私有的基于COM的通訊通道,目的是響應(yīng)IE事件。所以BHO最重要的是實(shí)現(xiàn)
IObjectWithSite 接口。IE 通過(guò)這個(gè)接口,傳遞自己的IUnknown接口,BHO存儲(chǔ)該接口,進(jìn)一步通過(guò)調(diào)用IObjectWithSite 提供的方法。
IE啟動(dòng)時(shí)候會(huì)依次執(zhí)行一些函數(shù),把BHO自己的函數(shù)注冊(cè)到這里,書(shū)面上稱之為連接點(diǎn)。這樣就能調(diào)用進(jìn)入 BHO自己的程序內(nèi)部了。
5.IObjectWithSite 介紹
主要有兩個(gè)函數(shù)要實(shí)現(xiàn):
a).HRESULT SetSite(IUnknown* pUnkSite) 接收ie瀏覽器的IUnknown指針。典型實(shí)現(xiàn)是保存該指針以備將來(lái)使用。
b).HRESULT GetSite(REFIID riid, void** ppvSite) 從通過(guò)SetSite()方法設(shè)置的場(chǎng)所中接收并返回指定的接口,典型實(shí)現(xiàn)是查詢前面保
存的接口指針以進(jìn)一步取得指定的接口。
6.下面通過(guò)一個(gè)實(shí)例來(lái)講解下
目標(biāo)是啟動(dòng)IE 訪問(wèn) http://www.aygfsteel.com/JAVA-HE/的時(shí)候彈出一個(gè)警告對(duì)話框。
a) 首先啟動(dòng)VC 2005 新建工程 ATL Project
工程名字: HelloWorld
Application Settings 中,Server type 選擇 Dynamic-link library (dll) 其他都不選。(簡(jiǎn)單總結(jié)為以atl 創(chuàng)建dll工程)
b) 其次在新建好的CLASS VIEW下選擇 工程 HelloWorld 右鍵 add -> class
選擇創(chuàng)建ATL Simple Object ,下一步 在short name中輸入 HelloWorldBHO 其他都自動(dòng)補(bǔ)齊不用管。
下一步在options中 :
Threading mode 選擇 Apartment ;
Aggregation 選擇 no;
Interface 選擇 Dual
support 選擇 IObjectWithSite(IE object support)
點(diǎn)擊Finish ,這是一個(gè)最簡(jiǎn)單的BHO已經(jīng)創(chuàng)建好了。
c) 目前要做的就是添磚了
編輯 HelloWorldBHO.h 發(fā)現(xiàn)系統(tǒng)已經(jīng)為你創(chuàng)建了 CHelloWorldBHO類, 在類的最下行有一個(gè)public: 沒(méi)后文,這里就是系統(tǒng)提示你要添加的代
碼了。
CHelloWorldBHO 中SetSite 已經(jīng)為你添加將瀏覽器的IUnknown 指針保存下來(lái)(見(jiàn)m_spUnkSite);。
現(xiàn)在要做的是注冊(cè)到連接點(diǎn)。
{
IObjectWithSiteImpl<CHelloWorldBHO>::SetSite(pUnkSite);
RegisterEventHandler(TRUE);
return S_OK;
}
繼承父類的 SetSite方法,顯示調(diào)用父類(SetSite)方法,添加一個(gè)注冊(cè)函數(shù)。(就是前面所述的,添加到連接點(diǎn))
{
CComPtr<IConnectionPoint>spCP;
m_spWebBrowser2 = m_spUnkSite;
if( m_spWebBrowser2 == NULL )
{
return E_FAIL;
}
CComQIPtr<IConnectionPointContainer,&IID_IConnectionPointContainer> spCPC(m_spWebBrowser2);
if(spCPC == NULL)
{
return E_FAIL;
}
// 查找到連接點(diǎn)
HRESULT hr = spCPC->FindConnectionPoint(DIID_DWebBrowserEvents2, &spCP);
if(FAILED(hr))
{
return hr;
}
if(inAdvise)
{
// 添加到連接點(diǎn) (注冊(cè))
hr = spCP->Advise(reinterpret_cast<IDispatch *>(this), &mCookie);
}else
{
// 反注冊(cè)
spCP->Unadvise(mCookie);
}
return hr;
}
其中成員聲明:
DWORD mCookie;
連接點(diǎn)注冊(cè)上去的索引,方便以后反注冊(cè)使用。
CComQIPtr<IWebBrowser2> m_spWebBrowser2;
m_spWebBrowser2 用來(lái)將瀏覽器IUnknown 指針轉(zhuǎn)變?yōu)?IWebBrowser2 ,并保存方便后面使用。
我們可以看到將 IDispatch 接口注冊(cè)上去了。 想想 COM組件應(yīng)用(2)——IUnknown 中寫(xiě)過(guò)的 QueryInterface的應(yīng)用,我們就知道
這里把IDispatch 接口注冊(cè)上去,說(shuō)明瀏覽器將可以調(diào)用IDispatch 的方法了。
查IDispatch 接口,主要核心在于實(shí)現(xiàn) Invoke 函數(shù)。
2 WORD wFlags, DISPPARAMS * pDispParams,
3 VARIANT * pvarResult, EXCEPINFO * pexcepinfo,
4 UINT * puArgErr)
5 {
6 USES_CONVERSION;
7 if(!pDispParams)
8 {
9 return E_INVALIDARG;
10 }
11 switch(dispidMember)
12 {
13 case DISPID_BEFORENAVIGATE2:
14 {
15 CComBSTR strUrl;
16 if( m_spWebBrowser2 != NULL )
17 m_spWebBrowser2->get_LocationURL(&strUrl);
18
19 if (strUrl == CComBSTR("http://www.aygfsteel.com/JAVA-HE/"))
20 {
21 ::MessageBox(NULL, _T("該網(wǎng)頁(yè)是我的blog!"),_T("Warning"),MB_ICONSTOP);
22 return S_OK;
23 }
24
25 break;
26 }
27
28 case DISPID_NAVIGATECOMPLETE2:
29 break;
30 case DISPID_DOCUMENTCOMPLETE:
31 break;
32 case DISPID_DOWNLOADBEGIN:
33 break;
34 case DISPID_DOWNLOADCOMPLETE:
35 break;
36 case DISPID_NEWWINDOW2:
37 break;
38 case DISPID_QUIT:
39 RegisterEventHandler(FALSE);
40 break;
41 default:
42 break;
43 }
44
45 return S_OK;
46 }
上面是這次實(shí)例的范例代碼。有興趣的朋友可以照著敲下,下面還是講下這個(gè)函數(shù)里面的一些點(diǎn):
I.最佳反注冊(cè)時(shí)機(jī)
case DISPID_QUIT:
RegisterEventHandler(FALSE);
在瀏覽器退出的時(shí)候,是最佳反注冊(cè)時(shí)機(jī),不然可能瀏覽器退出后,你的BHO還在運(yùn)行哦(有可能)
II. 有些宏需要引入頭文件
#include <exdisp.h >
#include <ExDispid.h>
III.當(dāng)要使用ATL 編碼轉(zhuǎn)換時(shí)候在函數(shù)開(kāi)頭引入 USES_CONVERSION; 宏,不過(guò)這里如果沒(méi)有轉(zhuǎn)換應(yīng)用,可以去掉。
根據(jù)支持BHO特性,為了不讓資源管理器加載該BHO,所以在dll main 函數(shù)里加入如下代碼判斷是否加載:
{
TCHAR pszLoader[MAX_PATH];
GetModuleFileName(NULL,pszLoader,MAX_PATH);
_tcslwr(pszLoader);
if(_tcsstr(pszLoader,_T("explorer.exe")))
{
return FALSE;
}
}