1.什么叫BHO

  Browser Helper Objects,"浏览器帮助者对象",以下皆简称BHO。

2.支持BHO特性的系统一览表:

Shell版本 操作系统版本 支持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注册表中的位置

  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{7C260B4B-F7A0-40B5-B403-

BEFCDC6A4C3B}
IE启动时候根据此项到HKEY_CLASSES_ROOT\CLSID下面找到对应BHO组件加载。

4.基本逻辑
 
  IE将自己的IUnKnown指针传递给BHO,BHO需要建立一个私有的基于COM的通讯通道,目的是响应IE事件。所以BHO最重要的是实现

IObjectWithSite 接口。IE 通过这个接口,传递自己的IUnknown接口,BHO存储该接口,进一步通过调用IObjectWithSite 提供的方法。

  IE启动时候会依次执行一些函数,把BHO自己的函数注册到这里,书面上称之为连接点。这样就能调用进入 BHO自己的程序内部了。

5.IObjectWithSite 介绍

主要有两个函数要实现:
 
  a).HRESULT SetSite(IUnknown* pUnkSite)  接收ie浏览器的IUnknown指针。典型实现是保存该指针以备将来使用。

  b).HRESULT GetSite(REFIID riid, void** ppvSite)  从通过SetSite()方法设置的场所中接收并返回指定的接口,典型实现是查询前面保

存的接口指针以进一步取得指定的接口。

6.下面通过一个实例来讲解下

 目标是启动IE 访问 http://www.blogjava.net/JAVA-HE/的时候弹出一个警告对话框。

a) 首先启动VC 2005 新建工程 ATL Project
 
   工程名字: HelloWorld
   Application Settings 中,Server type 选择 Dynamic-link library (dll) 其他都不选。(简单总结为以atl 创建dll工程)

b) 其次在新建好的CLASS VIEW下选择 工程 HelloWorld 右键 add -> class

   选择创建ATL Simple Object ,下一步 在short name中输入 HelloWorldBHO 其他都自动补齐不用管。
 
   下一步在options中 :

 Threading mode 选择 Apartment ;
 Aggregation 选择 no;
 Interface 选择 Dual
 support 选择 IObjectWithSite(IE object support)

点击Finish ,这是一个最简单的BHO已经创建好了。

c) 目前要做的就是添砖了

编辑 HelloWorldBHO.h 发现系统已经为你创建了 CHelloWorldBHO类, 在类的最下行有一个public: 没后文,这里就是系统提示你要添加的代

码了。

 
  CHelloWorldBHO 中SetSite 已经为你添加将浏览器的IUnknown 指针保存下来(见m_spUnkSite);。

  现在要做的是注册到连接点。
 

STDMETHOD(SetSite)(IUnknown *pUnkSite)
    {
        IObjectWithSiteImpl
<CHelloWorldBHO>::SetSite(pUnkSite);
        RegisterEventHandler(TRUE);
        
return S_OK;
    }

 

  继承父类的 SetSite方法,显示调用父类(SetSite)方法,添加一个注册函数。(就是前面所述的,添加到连接点)
 

HRESULT RegisterEventHandler(BOOL inAdvise)
    {
        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;
        }
        
        
// 查找到连接点

        HRESULT hr 
= spCPC->FindConnectionPoint(DIID_DWebBrowserEvents2, &spCP);

        
if(FAILED(hr))
        {
            
return hr;
        }

        
if(inAdvise)
        {
            
// 添加到连接点 (注册)
            hr = spCP->Advise(reinterpret_cast<IDispatch *>(this), &mCookie);
        }
else
        {
            
// 反注册
            spCP->Unadvise(mCookie);
        }
        
return hr;
    }

 

 其中成员声明:

 DWORD mCookie;
 连接点注册上去的索引,方便以后反注册使用。

 CComQIPtr<IWebBrowser2> m_spWebBrowser2;

 m_spWebBrowser2 用来将浏览器IUnknown 指针转变为 IWebBrowser2 ,并保存方便后面使用。

 我们可以看到将 IDispatch 接口注册上去了。 想想 COM组件应用(2)——IUnknown 中写过的 QueryInterface的应用,我们就知道

这里把IDispatch 接口注册上去,说明浏览器将可以调用IDispatch 的方法了。

 查IDispatch 接口,主要核心在于实现 Invoke 函数。 

 1 STDMETHODIMP Invoke(DISPID dispidMember,REFIID riid, LCID lcid,
 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.blogjava.net/JAVA-HE/"))
20                 {
21                     ::MessageBox(NULL, _T("该网页是我的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     }


 上面是这次实例的范例代码。有兴趣的朋友可以照着敲下,下面还是讲下这个函数里面的一些点:

 I.最佳反注册时机
  case DISPID_QUIT:
   RegisterEventHandler(FALSE);

 在浏览器退出的时候,是最佳反注册时机,不然可能浏览器退出后,你的BHO还在运行哦(有可能)

 II. 有些宏需要引入头文件
  #include <exdisp.h >
  #include <ExDispid.h>

 III.当要使用ATL 编码转换时候在函数开头引入 USES_CONVERSION; 宏,不过这里如果没有转换应用,可以去掉。


根据支持BHO特性,为了不让资源管理器加载该BHO,所以在dll main 函数里加入如下代码判断是否加载:
if(dwReason == DLL_PROCESS_ATTACH)
    {
        TCHAR pszLoader[MAX_PATH];

        GetModuleFileName(NULL,pszLoader,MAX_PATH);

        _tcslwr(pszLoader);

        
if(_tcsstr(pszLoader,_T("explorer.exe")))
        {
            
return FALSE;

        }
    }

posted on 2009-08-19 20:48 -274°C 阅读(2420) 评论(0)  编辑  收藏 所属分类: C++

只有注册用户登录后才能发表评论。


网站导航:
 

常用链接

留言簿(21)

随笔分类(265)

随笔档案(242)

相册

JAVA网站

关注的Blog

搜索

  •  

积分与排名

  • 积分 - 872805
  • 排名 - 37

最新评论