IE里的探索之定制浏览器好助手
(作者:青苹果工作室编译 2001年02月08日 14:00)
有些情况下你需要特制的、或多或少有些改变的浏览器。这种情况下,你有时候会基于 WebBrowser 控件开发一个完全定制的模块,实现按钮、标题以及用户界面需要的其它东西。这时,你可以自由地在这个浏览器中添加任何新的、非标准的功能。WebBrowser 控件只是浏览器的语法分析引擎。这就是说还有很多用户界面相关的任务必须由你完成:添加地址栏、工具条、历史、状态栏、频道和收藏夹等等。所以,要创建定制的浏览器,你必须编写两种代码:将 WebBrowser 控件变成类似于 Microsoft Internet Explorer 的全功能浏览器的代码和支持你的新功能的代码。如果有一种定制 Internet Explorer 的直接方式不是很好吗?浏览器助手对象 (BHO) 就是做这件事用的。
程序定制
历史上,定制程序行为的第一种方法是子类。通过这种方法,你能改变程序中给定的窗口处理消息的方式以获得不同的行为。这是一种原始的实现方式,然而因为受害者很少意识到,在很长一段时间内这是唯一的选择。
Microsoft Win32 API 出现时,不鼓励使用进程间的子类,而且它们的代码比较难写。然而,如果你有一颗勇敢的心,指针从来就吓不倒你;毕竟,你生活在系统挂钩的环境里,你也许会发现它其实很简单。但不总是这种情况。不管是多么聪明的编程,有一个问题就是每一个 Win32 进程运行在它自己的地址空间内,而有时打破这种进程的边界是不正确的。另一方面,这要求你倾尽全力完成这种编程。更为常见的是,定制可能是指程序本身在设计时就确定的指定功能。
后来,程序在众所周知的、预先指定的磁盘空间寻找附加模块,加载、初始化它们,然后让它们完成预先设计的工作。这就是 Internet Explorer 和它的助手对象的实际工作方式。
浏览器助手对象(BHO)是什么
从这个角度来看,Internet Explorer 就和任何其它使用自己内存空间的 Win32 程序一样。你能使用浏览器助手对象编写组件——进程内的组件对象模型 (COM) 组件——Internet Explorer 在每次启动时加载这些组件。这些组件和浏览器运行在相同的内存上下文里并且能在可用的窗口和模块里完成任何操作。例如,一个 BHO 能检测到浏览器的典型事件,如 GoBack、GoForward 和 DocumentComplete;访问浏览器的菜单和工具条并改变它们;创建窗口以显示当前可视页面上的附加信息;安装挂钩以监视消息和操作。简单地说,BHO 就像我们派出的潜入浏览器的间谍一样工作。
在我们深入到 BHO 核心细节之前,有些情况我需要说明。首先,BHO 连接在浏览器的主窗口上。实际上,这意味着每创建一个浏览器窗口,就创建了该对象的一个新实例。任何 BHO 实例同浏览器实例同时产生、同时消亡。其次,BHO 只存在于 Internet Explorer 4.0 以上版本。
如你运行带有 Active Desktop Shell Update (shell 版本 4.71) 的 Microsoft Windows 98、Windows 2000、Windows 95 或者 Windows NT 4.0 版操作系统,Windows Explorer 也支持 BHO。以后在讨论性能问题和实现压缩的 BHO 时我们会谈到相关内容。
最简单的情况下,BHO 是一个在特定注册表项下注册的进程内 COM 服务器。启动时,Internet Explorer 查找注册表并加载所有将其 CLSID 保存在此处的对象。浏览器初始化对象并要求它提供特定接口。如果发现了这样的接口,Internet Explorer 使用所提供的方法将它的 IUnknown 指针传递给助手对象。图1说明了这一过程。
图 1:Internet Explorer 如何加载并初始化浏览器助手对象。BHO site 是建立通讯所用的 COM 接口。
浏览器可能在注册表里发现一系列 CLSID,并为每一个 CLSID 创建一个进程内的实例。结果,这些对象被加载到浏览器的上下文,并且可以向内置部件一样使用。然而,由于浏览器本质上是基于 COM 的,加载到进程内部并不很重要。从另外一方面看,BHO 确实能实现一系列潜在的功能,比如说实现窗口的子类或安装线程局域挂钩,但 BHO 的主要目的是脱离浏览器核心操作。为了连接浏览器事件,或者说,将事件自动化,助手对象需要建立一个有权限的并且是基于 COM 的通讯通道。所以,BHO 应实现名为 IObjectWithSite 的接口。实际上, Internet Explorer 通过 IObjectWithSite 传递一个指向它自己的 IUnknown 接口指针。随后,BHO 就将这个指针保存起来,并通过它获得其它所需的接口,如 IWebBrowser2、IDispatch 和 IConnectionPointContainer。
可以从另一个方面,即 Internet Explorer 外壳扩展程序的角度来看待 BHO。像你知道的那样,Windows 外壳扩展程序是一个运行中的com,Windows Explorer装载后对文档进行特定操作。例如,显示它的上下文相关菜单时,加载的进程 内的 COM 服务程序。通过编写实现几个 COM 接口的 COM 模块,你就能在上下文 相关菜单中添加菜单项并适当地处理它们。外壳扩展程序必须以 Windows Explorer 能够找到的方式进行注册。浏览器助手对象遵从同样的模式 ;唯一的改变是要实现的接口。导致 BHO 被加载的触发条件是一个小差别。然而,除了实现的不同之外,像下表所说的那样,外壳扩展和 BHO 在本质上是一样的。
表 1. 外壳扩展程序和浏览器助手对象如何实现一般功能
如果你对外壳扩展程序感兴趣,请先参阅 MSDN 在线文档或 CD 文档。
助手对象的生命周期
像我们前面提到的那样,只有 Internet Explorer 支持 BHO。如果你运行了不低于版本 4.71 的外壳,你的 BHO 也可以被 Windows Explorer 载入。这样可以通过一个单一的浏览器并基于同样的用户经验同时浏览 Web 和本地磁盘。下表提供对当前可用的各种外壳版本的一个面向产品的概览。外壳的版本号取决于保存在 shell32.dll 中的版本信息。
表 2. 不同外壳版本对浏览器助手对象的支持
浏览器助手对象在浏览器的主窗口将要显示出来时加载,在窗口消失时卸载。你打开的浏览器窗口越多,创建的 BHO 实例也就越多。即使以命令行方式启动浏览器它也被加载。一般情况下,BHO 实例的数目和运行的 explorer.exe 或 iexplorer.exe 的数目一样多。如果你在文件夹选项里设置了“在不同窗口打开不同文件夹”,每次你打开一个新的文件夹时都会加载 BHO。
图 2. 使用这一设置,每打开一个文件夹就运行 explorer.exe 的一个单独实例 并加载注册了的 BHO。
然而,需要注意的是,这种情况仅仅发生在你从桌面上“我的电脑”图标开始打开文件夹的时候。在这种情况下,每次你转移到另外的文件夹时外壳都调用 explorer.exe。你在两栏视图中开始浏览时不会发生这种情况。实际上,你改变文件夹时外壳并不是启动浏览器的一个新实例,而是简单的创建嵌入视图对象的一个实例。特别是你在地址栏里输入一个新名字以改变文件夹时,无论 Windows Explorer 的视图是一栏还是两栏,浏览都在同一窗口内进行。
对 Internet Explorer 来说情况就简单多了。只有你多次显式地运行 iexplorer.exe 才会产生多个拷贝。当你从 Internet Explorer 中打开新窗口时,每个窗口在一个新的线程中复制,而不是创建一个新的进程,这样就不会重新加载 BHO。
尤其,BHO 最令人感兴趣的特征就是它们是动态的。每次打开 Window Explorer 或 Internet Explorer 的窗口时,它们从注册表里读取已安装的助手对象的 CLSID,然后进行处理。如果你编辑打开浏览器的不同实例的