浏览 11722 次
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-03-03
最后修改:2010-05-11
首先谈BHO的开发工具,我偏向使用VC++(unmanaged C++) 作为开发工具,因为Java JVM或.Net CLR的虚拟机是个很笨重的东西,也是内存杀手, 并不具备写plugin的快捷轻巧的特点.个人并不喜欢将其作为Plug-in的开发平台,不过我会有另文说明用C#开发BHO的全过程, 作为那些偏重开发效率的同学的参考. 其次是类库的选择,我倾向利用“活动模板库”(ATL) 来开发使用 C++ 的 BHO。之所以使用 ATL,是因为它方便地实现了我们可以按需进行扩展的基本样板。尽管使用“Microsoft 基础类”(MFC) 或 Win32 API 和 COM)也可以创建BHO,但 ATL 是为我们提供了自动处理许多细节的轻型库,包括建立含有 BHO 类标识符 (CLSID) 的注册表。 ATL 的另一个优势在于它的 COM智能感知指针类(例如,CComPtr 和 CComBSTR),这些类可管理 COM 对象的生命周期。例如,CComPtr 在赋值时会调用 AddRef,而在对象被销毁或超出范围时会调用 Release。智能指针简化了代码并且有助于避免内存泄漏。当在单个方法范围内使用时,它们的稳定性和可靠性尤为有用。 介绍完ATL, 我们也简单介绍一下BHO. BHO是将自定义功能添加到 Internet Explorer 的轻型 DLL 扩展,除了IE, BHO 还可以将功能添加到 Windows 资源管理器外壳程序. BHO 通常并不提供其自身的任何用户界面 (UI)。它们而是通过在后台响应浏览器事件和用户输入数据来发挥作用。例如,BHO 可以拦截弹出窗口、自动填充窗体或为鼠标手势添加支持。 有一种常见误解认为工具栏扩展项需要 BHO.但如果将 BHO 与工具栏配合使用,则可以实现更丰富的用户体验。(关于IE工具栏的编程,在另一篇文章中说明). BHO 的生命周期与它所交互的浏览器实例的生命周期相等。在 IE 6 和早期版本中,这意味着要为每个新的顶层窗口创建(和销毁)一个新 BHO。在IE 7中则是为每个选项卡都创建和销毁一个新 BHO。 BHO 必须实现 IObjectWithSite 接口, 该接口提供了两个方法GetSite和SetSite。根据MSDN的说明: GetSite: Gets the last site set with IObjectWithSite::SetSite. If there is no known site, the object returns a failure code. SetSite: Provides the site's IUnknown pointer to the object. 我们主要是对后者进行调用,此方法方便了与 Internet Explorer 的初始通信,并会在其将要释放时通知 BHO。我们实现此接口,然后将 BHO 的 CLSID 添加到注册表中,就可以创建一个简单的浏览器扩展。过程如下: 1. 在Visual Studio中,选择VC++中的ATL项目, 创建一个新的项目MySolutionPlugin, 在随后的向导中,确认Server Type是Dll, Visual Studio会为我们创建程序的模板. 2. 为该项目添加我们的程序主体, (不熟悉visual studio的同学在资源浏览器里的右键菜单里选 add-->class, 可别选到New Item), 类型选ATL Simple Object , short name命名为RayBHO,各项属性如下: a) “线程模型” ---“Apartment” b) “聚合”---“否” c) “接口”---“双重” d) “支持”---勾上“IobjectWithSite”。 具体的含义请参考MSDN. 一般来说,Internet Explorer 至少调用SetSite方法两次: 一次用于建立连接,另一次则是在浏览器退出时。我们 BHO 中的 SetSite 实现将执行以下操作: •存储对站点的引用。在初始化期间,浏览器将 IUnknown 指针传递给顶层 WebBrowser 控件,然后 BHO 将对它的引用存储在一个专用成员变量中。 •释放目前被占用的站点指针。Internet Explorer 传递 NULL 时,BHO 必须释放所有接口引用并且断开与浏览器的连接。 要实现SetSite,我们需手工在添加一个public的方法: STDMETHOD(SetSite)(IUnknown * pUnkSite); STDMETHOD 宏是将方法标记为虚方法并且确保其具有适用于公共 COM 接口的调用约定的一个ATL 约定, 它有助于区分 COM 接口和该类中可能存在的其他公共方法。其实现成员方法时应相应使用 STDMETHODIMP 宏。同时我们需要声明一个私有变量来保存Browser的指针" CComPtr<IWebBrowser2> m_spWebBrowser;//保存Browser指针的私有变量 然后是SetSite的实现 STDMETHODIMP CRayBHO::SetSite(IUnknown*pUnkSite) { if(pUnkSite!=NULL) { //缓存指向IWebBrowser2的指针。 pUnkSite->QueryInterface(IID_IWebBrowser2,(void**)&m_spWebBrowser); } else { //在此释放缓存的指针和其他资源。 m_spWebBrowser.Release(); } //返回基类实现 return IObjectWithSiteImpl::SetSite(pUnkSite); } 从上面的介绍我们知道, 初始化期间,浏览器将传递一个对其顶层 IWebBrowser2 接口(我们对其进行缓存处理)的引用。浏览器关闭时将传递 NULL,为避免内存泄漏和循环引用计数,此时释放所有指针和资源非常重要。最后,我们调用基类实现以便继续执行接口合约的其余部分。 加载DLL 后,系统将通过 DLL_PROCESS_ATTACH 通知调用 DllMain 函数。由于 Internet Explorer 大量使用多线程,因此,对 DllMain 的频繁的 DLL_THREAD_ATTACH 和 DLL_THREAD_DETACH 通知会降低扩展和浏览器进程的整体性能。 如果BHO 不需要线程级的跟踪,我们可以在 DLL_PROCESS_ATTACH 通知期间调用 DisableThreadLibraryCalls 以避免新线程通知的额外开销。修改DllMain.cpp 中的DllMain函数: extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { if(dwReason==DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hInstance); } return _AtlModule.DllMain(dwReason,lpReserved); } 要使BHO工作,我们还需要把BHO 的 CLSID 添加到注册表中。此条目会将 此DLL 标记为浏览器帮助程序对象,并使 Internet Explorer 在启动时加载 BHO。我们可以在MySolutionPlugin.idl中找到该BHO的CLSID.幸运的,Visual Studio会帮助我们实现这些, 你看到: importlib("stdole2.tlb"); [ uuid(057F3E68-6C2E-40A5-A641-E8CF9D6766F3), helpstring("RayBHO Class") ] 您的机器的CLSID可能有所不同, 接着打开RayBHO.rgs文件,添加入: HKLM { NoRemove SOFTWARE { NoRemove Microsoft { NoRemove Windows { NoRemove CurrentVersion { NoRemove Explorer { NoRemove 'Browser Helper Objects' { ForceRemove {057F3E68-6C2E-40A5-A641-E8CF9D6766F3} = s 'RayBHO Class' { val NoExplorer = d '1' } } } } } } } } 这一段是为了在注册表里添加一个双字节的NoExplorer=1的键,不让Windows Explorer加载该BHO,因此该BHO只能在ie中运行. 你可以编译这个BHO. 如果一切正常, 你可以在IE的管理加载项里看到这个BHO. 如果不幸报错: 该BHO无法被注册,根据我的经验,原因大概有2类,可以依次检查 1. 你是否有管理员权限以修改注册表,如不是管理员身份,可以在菜单上右击Microsoft Visual studio 2008,从右键菜单中选择"运行方式"... 2. 你的注册表条目语法是否正确,或者含有非法字符. 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-03-03
然后呢,没发完啊,lz,好文不要TJ
|
|
返回顶楼 | |
发表时间:2009-03-04
文中提到的n多“另一篇文章”在哪里??啥时候能出来。
很实用的文章,支持。 |
|
返回顶楼 | |
发表时间:2009-03-04
什么时候弄好呀!~!
|
|
返回顶楼 | |
发表时间:2010-01-04
好麻烦,还是给chrome写扩展方便。。。
|
|
返回顶楼 | |
发表时间:2010-01-04
最后修改:2010-01-04
mikeandmore 写道 好麻烦,还是给chrome写扩展方便。。。
这也不见得,中间代码大同小异,json=注册表,其他对象扩展c++和javacript几乎一样(不停的操作对象的属性和方法)。 chrome的那些javascript扩展代码,c++全能写出来,反之c++能搞的,javacsript就不成,必要时还得借助napi. 比如说,你让chrome扩展连个数据库看看,中间要做的工作(弄个json响应啦,用xmlhttp啦)就多了去了看着简单,未必简单。 譬如我给同事做的,用Exel里的东东去自动填写form,chrome扩展马上捉襟见肘。 |
|
返回顶楼 | |
发表时间:2010-01-04
ray_linn 写道 mikeandmore 写道 好麻烦,还是给chrome写扩展方便。。。
这也不见得,中间代码大同小异,json=注册表,其他对象扩展c++和javacript几乎一样(不停的操作对象的属性和方法)。 chrome的那些javascript扩展代码,c++全能写出来,反之c++能搞的,javacsript就不成,必要时还得借助napi. 比如说,你让chrome扩展连个数据库看看,中间要做的工作(弄个json响应啦,用xmlhttp啦)就多了去了看着简单,未必简单。 譬如我给同事做的,用Exel里的东东去自动填写form,chrome扩展马上捉襟见肘。 这个权衡吧,能搞和搞这舒服是两回事。。。。 PS COM对象是不是还要考虑weakref啊。。。不是refcounter么 |
|
返回顶楼 | |
发表时间:2010-01-04
mikeandmore 写道 PS COM对象是不是还要考虑weakref啊。。。不是refcounter么 BHO里啥时候出现weakref呢? |
|
返回顶楼 | |
发表时间:2010-01-04
ray_linn 写道 mikeandmore 写道 PS COM对象是不是还要考虑weakref啊。。。不是refcounter么 BHO里啥时候出现weakref呢? 哦,不知,但是据说写DOM扩展就要考虑了,因为js可以用DOM,js有closure,然后会有环形引用。。。-,- |
|
返回顶楼 | |