你是不是gXj很有意思Sr6仅仅造型

有时你可能需要一个定制版本嘚浏览器。在这种情况下你可以自由地把一些新颖但又不标准的特征增加到一个浏览器上。结果你最终有的只是一个新但不标准的浏覽器。Web浏览器控件只是浏览器的分析引擎这意味着仍然存在若干的与用户接口相关的工作等待你做――增加一个地址栏,工具栏历史記录,状态栏频道栏和收藏夹等。如此要产生一个定制的浏览器,你可以进行两种类型的编程――一种象微软把Web浏览器控件转变成一個功能齐全的浏览器如Internet Explorer;一种是在现有的基础上加一些新的功能如果有一个直接的方法定制现有的Internet Explorer该多好?BHO(Browser Helper Objects我译为"浏览器帮助者对潒",以下皆简称BHO)正是用来实现此目的的

以前,定制一个软件的行为主要是通过子类化方法实现的 通过这种办法,你可以改变一个窗ロ的外表与行为子类化虽然被认为是一种有点暴力方式――受害者根本不知道发生的事情――但它还是长时间以来的唯一的选择。

随着微软Win32 API的到来进程间子类化不再被鼓励使用并愈发变得困难起来。当然如果你是勇敢的--指针从未吓倒你,而最重要的是如果你已经游刃于系统钩子之间,你可能觉得这一问题太简单了 但是情形并不总是这样。暂放下这点不管问题在于每一个进程运行在自己的地址空間中,而且打破进程边界略微有些不正确性 另一方面, 你可能需要对定制进行更好的管理更经常情况下,定制可能是程序本身强烈要求实现的

在后者情况下,已安装的软件只需在既定的磁盘位置查询另外的组件模块然后装载、设定初值,最后让它们自由地按照既定嘚设计工作这正是Internet Explorer浏览器和它的BHO所要实现的。

Explorer同普通的Win32程序没有什么两样借助于BHO,你可以写一个进程内COM对象这个对象在每次启动时嘟要加载。这样的对象会在与浏览器相同的上下文中运行并能对可用的窗口和模块执行任何行动。例如一个BHO能够探测到典型的事件,洳GoBack、GoForward、DocumentComplete等;另外BHO能够存取浏览器的菜单与工具栏并能做出修改还能够产生新窗口来显示当前网页的一些额外信息,还能够安装钩子以监控一些消息和动作简而言之, BHO的工作如我们打入浏览器领地的一位间谍(注意这是微软允许的合法工作)

在进一步了解BHO细节之前,有幾点我需要进一步阐述首先,BHO对象依托于浏览器主窗口实际上,这意味着一旦一个浏览器窗口产生一个新的BHO对象实例就要生成。任哬 BHO对象与浏览器实例的生命周期是一致的其次, BHO仅存在于Internet Explorer 4.0及以后版本中

Internet Explorer浏览器初始化这一对象并要求某一接口功能。如果发现这一接ロ Internet Explorer使用其提供的方法传递 IUnknown 指针到BHO对象。见图一:

图一 ie浏览器如何装入和初始化BHO对象BHO场所(site)是用于实现通信的COM接口

浏览器可能在注册表中发现一系列的CLSID,并由此为每个CLSID建立一个进程中实例结果是,这些对象被装载至浏览器上下文中并运行起来好象它们是本地组件一樣。但是由于Internet Explorer的COM特性,即使被装入到它的进程空间中于事(你的野心实现)也不一定会有多大帮助用另一说法, BHO的确能够做许多潜在嘚有用的事情如子类化组成窗口或者安装线程局部钩子,但是它确实远离浏览器的核心活动为了钩住浏览器的事件或者自动化浏览器,BHO需要建立一个私有的基于COM的通讯通道为此,该BHO应该实现一个称为IObjectWithSite的接口事实上,通过接口IobjectWithSite Internet Explorer 可以传递它的IUnknown

另外一种分析BHO对象的途径與Internet Explorer外壳扩展有关。我们知道一个WINDOWS外壳扩展即是一个进程内的COM服务器,它在Windows资源管理器执行某种动作时装入内存――如显示上下文菜单通过建立一个实现几个COM接口的COM模块,你就给上下文菜单加上一些项并能预以正确处理一个外壳扩展必须以Windows资源管理器能够发现的方法注冊。一个BHO对象遵循同样的模式――唯一的改变在于要实现的接口然而,尽管实现方式有所不同外壳扩展与 BHO 仍有许多共同的特点。如下表一:

表一 外壳扩展与 BHO相近特性比较

在某类文档上的用户动作(即单击右键)
参考计数达到0的几秒之后 导致它加载的窗口关闭时
常常是为┅个COM服务器设置的入口处另加的入口依赖于外壳类型及它要应用至的文档类型 常常是为一个COM服务器设置的入口处,另加一个把它申请为BHO嘚注册入口

如果你对SHELL扩展编程有兴趣的话可以参考MSDN有关资料。

前面已经说过BHO不仅仅为Internet Explorer所支持。如果你在使用外壳 4.71或者更高版本你的BHO對象也会被Windows资源管理器所加载。下表二展示了我们可以使用的不同版本的外壳产品情况Windows外壳版本号存于库文件shell32.dll中。

表二 不同版本的Windows外壳對于BHO的支持情况

BHO对象随着浏览器主窗口的显示而装入随着浏览器主窗口的销毁而缷载。如果你打开多个浏览器窗口多个BHO实例也一同产苼。

无论浏览器以什么样的命令行启动BHO对象都被加载。举例来说即使你只是想要见到特定的 HTML 页或一个给定的文件夹,BHO对象也被加载┅般地,当 explorer.exe 或 iexplore.exe 运行的时候BHO都要被考虑在内。如果你设置了"Open each folder in its own window"(对每一个文件夹以一个独立窗口打开)文件夹选项那么你每次打开一个文件夹,BHO对象都要被加载见图二。

图二 经过这样设置你每次打开一个文件夹时,执行一个独立的explorer.exe实例并装入已注册的BHO对象。

但是注意这种情形仅适于当你从桌面上的"我的电脑"图标中打开文件夹的情况。在这种情况下每次你移到另外一个文件夹时外壳都要调用explorer.exe。这种凊况在你同时用两个窗格进行浏览时是不会发生的事实上,当你改变文件夹时外壳是不会启动浏览器的新的实例的而仅是简单创建嵌叺视图对象的另外一个实例。奇怪的是如果你在地址栏中输入一个新的名字来改变文件夹时,在同一个窗口中同样可以达到浏览之目的无论Windows资源管理器视图是单个的还是双视图形式。

对于Internet Explorer的情形事情要更简单一些。只有你显式地多次运行iexplore.exe浏览器时你才有多个Internet Explorer的拷贝。当你从Internet Explorer中打开新的窗口时每一个窗口在一个新的线程中被复制而不是创建一个新的进程,因此也就不需要重新载入BHO对象

首先,BHO最有趣的地方是它是极度动态的。每次Windows资源管理器或者Internet Explorer打开装载器从注册表中读取已安装的BHO对象的CLSID然后处理它们。如果你在打开的浏览器哆个实例中间编辑注册表的话你可以随着多个浏览器拷贝的载入而装入多个不同的BHO。 这就是说如果你选择从头创建一个新的属于自己嘚浏览器,那么你可以把它内嵌在一个Visual Basic或者MFC框架窗口中同时你有相当的机会来灵活安排浏览程序。如果它们能满足你的需要的话你可鉯依赖于Internet Explorer的强大的功能并且加上你想要的尽可能多的插件。

从一个高起点来看BHO即是一个DLL,它能够依附于Internet Explorer浏览器的一个新建的实例在某些情况下也适用于Windows资源管理器。

一般地一个场所(site)是一个中间对象,它位于容器对象与被包容对象之间通过它,容器对象管理被包嫆对象的内容也因此使得对象的内部功能可用。为此容器方要实现接口IoleClientSite,被包容对象要实现接口IOleObject 通过调用IOleObject提供的方法,容器对象使嘚被包容对象清楚地了解其HOST的环境

一旦容器对象成为Internet Explorer(或是具有WEB能力的Windows资源管理器),被包容对象只需实现一个轻型的IObjectWithSite接口该接口提供了以下方法:

接收ie浏览器的IUnknown指针。典型实现是保存该指针以备将来使用.
从通过SetSite()方法设置的场所中接收并返回指定的接口,典型实现是查询前面保存的接口指针以进一步取得指定的接口

对BHO 的唯一严格的要求正在于必须实现这一个接口。 注意你应该避免在调用以上任何一個函数时返回E_NOTIMPL 要么你不实现这一接口,要么应保证在调用这些方法时进行正确地编码

六、构造自己的BHO对象

一个BHO对象就是一个进程中服務器DLL,选用ATL创建它是再恰当不过的了我们选择ATL的另外一个原因是因为它已经提供了缺省的而且提供了IObjectWithSite接口的足够好的实现。另外在ATL COM 向導本地支持的已定义好的对象类型当中,有一个就是Internet Explorer对象,这正是一个BHO应该具有的类型一个 ATL Internet Explorer 对象,事实上是一个简单对象――也就是說是一个支持IUnknown和自注册,还有接口IObjectWithSite的COM 服务器如果你在ATL工程中添加一个这样的对象,并调用相应的类CViewSource你将从向导中得到下列代码:

正洳你所见,向导已经使类从接口IObjectWithSiteImpl继承这是一个ATL模板类,它提供了接口IObjectWithSite的基本实现一般情况下,没有必要重载成员函数GetSite()取而代之的是, SetSite() 实现代码经常需要加以定制ATL实际上仅仅把一个IUnknown接口指针存储在成员变量m_spUnkSite中。

在文章的剩余部分我将讨论一个 BHO 的相当复杂而丰富的例孓。该BHO对象将依附于Internet Explorer并显示一个文本框来显示当前正浏览的网页源码。 该代码窗口将 随着你改变网页而自动更新如果浏览器显示的鈈是一个HTML网页时,它将变灰你对于原始HTML代码的任何改动立即反映在浏览器中。HTML (DHTML)使得这一看似魔术般的实现成为可能该代码窗口可被隐藏和通过按动热键重现。 在可见情况下它与Internet Explorer共享整个桌面空间,见图三

图三 BHO对象在使用中。它依附于Internet Explorer并显示一个窗口来显示当前囸浏览的网页源码。还允许你源码进行修改

本例子的关键点在于存取Internet Explorer的浏览机制,其实它只不过是WebBrowser控件的一个实例而已这个例子可以汾解为以下五步来实现:

3.捕捉Web浏览器的特定事件;

4.存取当前文档对象,确定它是一份HTML类型的文件;

5.管理对话框窗口以实现HTML源码的显示;

第┅个步骤是在DllMain()中完成的SetSite()是取得指向WebBrowser对象指针的适当位置。请详细分析以下步骤

七、探测谁在调用这个对象

如前所述,一个BHO对象会被Internet Explorer或鍺Windows资源管理器(前提:外壳版本4.71或者更高)所加载所以我专门设计了一个BHO来处理HTML网页,因此这个BHO与资源管理器毫无关系如果一个Dll不想被调用者一起加载,只需在DllMain()中实现了探明谁在调用该对象后返回FALSE即可参看下面代码:

一旦知道了当前进程是Windows资源管理器,可立即退出

紸意,再多加一些条件语句是危险的!事实上另外一些进程试图装入该DLL时将被放弃。如果你做另外一个试验比方说针对Internet Explorer的执行文件iexplorer.exe,這时第一个受害者就是regsvr32.exe(该程序用于自动注册对象)

八、与Web浏览器取得联系

SetSite()方法正是BHO对象被初始化的地方,此外在这个方法中你可以執行所有的仅仅允许发生一次的任务。当你用Internet Explorer打开一个URL时你应该等待一系列的事件以确保要求的文档已完全下载并被初始化。唯有在此時你才可以通过对象模型暴露的接口(如果存在的话)存取文档内容。这就是说你要取得一系列的指针第一个就是指向IWebBrowser2(该接口用来生荿WebBrowser对象)的指针。第二个指针与事件有关该模块必须作为一个浏览器的事件侦听器来实现,目的是为接收下载以及与文档相关的事件下媔用ATL灵敏指针加以封装:

为了取得IWebBrowser2接口指针,你可以进行查询当然也可以在事件刚刚发生时查询IConnectionPointContainer。这里SetSite()检索了浏览器的句柄HWND,并且在當前线程中安装了一个键盘钩子HWND用于后面Internet Explorer窗口的移动或尺寸调整。这里的钩子用来实现热键功能用户可以按动热键来显示/隐藏代码窗ロ。

当你导向一个新的URL时浏览器最需要完成的是两种事件:下载文档并为之准备HOST环境。也就是说它必须初始化某对象并使该对象从外蔀可以利用。针对不同的文档类型或者装入一个已注册的Microsoft ActiveX? 服务器来处理该文档(如Word对于.doc文件的处理)或者初始化一些内部组件来分析文檔内容并生成和显示该文档。对于HTML网页就是这样其内容由于DHTML对象作用而变得可用。当文档全部下载结束DownloadComplete事件被激活。这并不是说这樣利用对象模型就可以安全地管理文档的内容了。事实上DocumentComplete 事件仅指明一切已经结束,文档已准备好了

为了截获浏览器发出的事件 BHO需要通过IConnectionPoint 接口连接到浏览器上 并且实现传递接口IDispatch指针以处理各种事件。现在利用前面取得的IConnectionPointContainer指针来调用FindConnectionPoint方法――它返回一个指针指向连接点对潒(正是通过这个连接点对象来取得要求的外向接口此时是DIID_DWebBrowserEvent2)。 下列代码显示了连接点的发生情况:

通过调用接口IConnectionPoint的Advise() 方法 BHO告诉浏览器咜对它产生的事件很感兴趣。 由于COM事件处理机制所有这些意味着BHO把IDispatch接口指针提供给浏览器。浏览器将回调IDispatch接口的Invoke() 方法以事件的ID值作为苐一参数:

切记,当事件不再需要时应该使之与浏览器分离。如果你忘记了做这件事情BHO对象将被锁定,即使在你关闭浏览器窗口之后很明显,实现分离的最佳时机是收到事件OnQuit时

此时,该BHO已经有一个参照指向Internet Explorer的Web浏览器控件并被连接到浏览器控件以接收所有它产生的事件当网页被全部下载并正确初始化后,我们就可以通过DHTML文档模型存取它Web浏览器的文档属性返回一个指向文档对象的IDispatch接口的指针:

get_Document() 方法取得的仅仅是一个接口指针。我们要进一步确定在IDispatch 指针背后存在一个HTML文档对象用VB实现的话,可以用下面代码:

现在要了解一下get_Document()返回的IDispatch指針 Internet Explorer不仅仅是一个HTML浏览器,而且还是一个ActiveX文档容器 这样一来,难以保证当前浏览对象就是一个HTML文档不过办法还是有的――你想,如果IDispatch指针真正指向一个HTML文档查询IHTMLDocument2 接口一定成功。
IHTMLDocument2接口包装了DHTML对象模型用来展现HTML页面的所有功能下面代码实现这些功能:

现在考虑如何获得當前显示窗口的源代码。正如一个HTML页把它所有的内容封装在标签中DHTML对象模型要求你取得一个指向Body对象的指针:

奇怪的是,DHTML对象模型不让伱取得标签之前的原始内容如。其内容被处理并存于一些属性中但你还是不能从HTML原始文件中提取这部分的RAW文本。这过仅从BODY部分取得嘚内容足够了。为了取得包含在…间的HTML代码部分可以把outerHTML属性内容读取到一个BSTR变量中:

在此基础上,在代码窗口中显示源码就是一种简单嘚事情了:生成一个窗口进行字符的UNICODE至ANSI转化和设置编辑框控件的问题。下面代码实现这些功能:

因为我要运行这段代码来响应DocumentComplete事件通知每个新的页自动地而且敏捷地被处理。DHTML对象模型使你能够随意修改网页的结构但这一变化在按F5刷新后全部复原。你还要处理一下DownloadComplete事件鉯刷新代码窗口 (注意 DownloadComplete 事件发生在 DocumentComplete事件之前)。你应该忽略网页的首次DownloadComplete事件而是在执行刷新动作时才关注这一事件。布尔成员变量m_bDocumentCompleted正是用來区别这两种情形的

用来显示当前HTML页原始码的代码窗口涉及另外一个ATL 基本编程问题-对话框窗口,它位于ATL对象向导的"Miscellaneous"选项卡下

我调整了玳码窗口的大小来响应WM_INITDIALOG消息,使它占居桌面空间的下部区域正好是在任务栏的上面。在浏览器启动时你可以选择显示或不显示这个窗口缺省情况下是显示的,但这可以通过清除"Show window at startup"复选框项来实现当然喜欢的话,你可以随时关闭按键F12即可重新显示代码窗口。F12是通过在SetSite()中咹装的键盘钩子实现的启动环境存于WINDOWS注册表中,我选择外壳库文件shlwapi.dll中函数SHGetValue来实现注册表的读写操作这同使用Reg开头的Win32函数操作相比,简單极了请看:

这个DLL文件是同Internet Explorer 4.0 和活动桌面的诞生一起产生的,是WIN98及以后版本的标准组成你可以放心使用。

因为BHO 是一个COM 服务器所以既应該作为COM 服务器注册又应该作为BHO对象注册。ATL向导自动生成.rgs文件第一种情况的注册就免除了。下面的文件代码段是用来实现作为BHO对象注册的(CLSID為例中生成)

注意ForceRemove一词能够实现在卸载对象时删除这一行相应的键值。BHO键下聚集了所有的BHO对象对于这么多的一串家伙是从来不作缓冲调鼡的。这样以来安装与测试BHO就是不费时的事情了。

本文描述了BHO对象通过它你可以把自己的代码注入浏览器的地址空间中。你必须做的倳情是写一个支持IObjectWithSite 接口的COM 服务器在这一点上,你的BHO对象可以实现浏览器机制范围内的各种合法目的本文所及示例涉及了COM事件,DHTML对象模型以及WEB浏览器编程接口虽然内容稍宽一些,但它正显示了现实世界中的BHO对象的应用如,你想知道浏览器在显示什么那么您就需要了解接收事件并要熟悉WEB浏览器才行。

另外:Windows资源管理器也是与BHO对象交互的这一点在编程时要特别注意。本文所附源程序为MSDN所带在Windows2000/VC6下调试通过(编译通过后,重新启动IE即得到结果)

我要回帖

 

随机推荐