VC++开发BHO插件——定制你的浏览器
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
文章原始出处
正文
在Windows操作系统上,我们最常见的浏览器有两种:文件浏览器(exploer.exe,应用于文件系统)和Internet浏览器(iexplore.exe,应用于互联网资源)。
由于这两个浏览器功能强大,而且又与Windows操作系统捆绑销售,最终也就成为了浏览器的标准。
但有时候,为了给浏览器加入一些新的特性,我们往往会重新设计一个自己的浏览器。
新的浏览器模仿标准浏览器的大部分功能,同时加入新特性。
这种做法最直观,但实际上也是相对于微软的重复劳动,且工作量比较大。
其实,使用BHO插件,一切都变得很简单。
BHO(Browser Help Objects),是实现了特定接口的COM组件。
开发好的BHO插件在注册表特定的位置注册好后,每当微软的浏览器启动,BHO实例就会被创建。
在浏览器工作的工程中,BHO会接收到很多事件,比如浏览器浏览新的地址、前进或后退、生成新的窗口、浏览器退出等等;BHO可以在这些事件的响应中实现与浏览器的交互。
下面,我们首先来介绍一下BHO的工作原理。
上面我们已经提到,BHO是COM组件,而且一定实现了IObjectWithSite接口。
这些组件除了在注册表中注册为COM Server外,还必须将它们的CLSID在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\ CurrentVersion\Explorer\Browser Helper Objects下注册为子键。
微软在设计浏览器的时候,已经给这些组件预留了空间。
每当浏览器启动时,浏览器会首先在上述注册表位置查看是否有注册的BHO CLSID;如果有则分别创建一个实例,并对BHO实例进行初始化,建立交互连接。
(注:BHO实例只有在创建它的浏览器窗口销毁时才被释放。
)下图演示了BHO 的创建过程:
成功创建的BHO,不仅可以得到各种标准的浏览器操作事件,并做出响应;还可以定制浏览器的菜单、工具条等界面元素;更或者可以安装钩子函数,监视浏览器的一举一动。
值得注意的是,使用BHO插件,Internet浏览器要求在4.0以上版本;如果是文件浏览器,操作系统要求是Windows 95/98/2000或Window NT 4.0以上版本,并且Shell的版本在4.71以上。
下面是支持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和文件浏览器
接下去,笔者就来介绍一下如何开发BHO插件,开发环境为VC6.0(使用ATL),安装Platform SDK中的Internet Development SDK。
首先,启动VC的ATL COM AppWizard,生成一个项目名为BhoPlugin,其余均采用默认设置。
接着,我们就来分步详细阐述。
第一步,增加一个ATL Object到该项目中。
VC菜单Insert->New ATL Object…,在弹出的对话框中选择“Internet Explorer Object”,输入COM类名(在Short Name后输入EyeOnIE,其它各项会自动生成)。
完成后,我们可以看到CEyeOnIE类有一个基类IObjectWithSiteImpl,这个就是实现IObjectWithSite接口的模版类。
第二步,实现IObjectWithSite的接口方法。
在这之前,我们要先定义几个成员变量:CComQIPtr mWebBrowser2,(需要加入#include "ExDisp.h"),用以保存浏览器组件的指针;DWORD mCookie,用以保存与浏览器的连接ID。
IObjectWithSite有两个接口方法:SetSite和GetSite。
我们只需重载SetSite就行了。
在EyeOnIE.h中增加函数声明STDMETHOD(SetSite)(IUnknown *pUnkSite),在EyeOnIE.cpp实现如下:STDMETHODIMP CEyeOnIE::SetSite(IUnknown *pUnkSite)
{
USES_CONVERSION;
if (pUnkSite)
{
mWebBrowser2 = pUnkSite;
if (mWebBrowser2)
{
return RegisterEventHandler(TRUE);
}
}
return E_FAIL;
}
HRESULT CEyeOnIE::RegisterEventHandler(BOOL inAdvise)
{
CComPtr<IConnectionPoint> spCP;
// Receives the connection point for WebBrowser events
CComQIPtr<IConnectionPointContainer, &IID_IConnectionPointContainer> spCPC(mWebBrowser2);
HRESULT hr = spCPC->FindConnectionPoint(DIID_DWebBrowserEvents2, &spCP);
if (FAILED(hr))
return hr;
if (inAdvise)
{
// Pass the event handlers to the container
hr = spCP->Advise(reinterpret_cast<IDispatch*>(this), &mCookie);
}
else
{
spCP->Unadvise(mCookie);
}
return hr;
}
我们可以看到,SetSite的参数实际上指向的是浏览器组件。
在SetSite实现中,我们首先保存浏览器组件指针,然后将该BHO向浏览器注册为事件处理器。
第三步,实现IDispatch接口方法。
事件处理也就在IDispatch::Invoke中实现(各个事件的
ID在ExDispID.h中定义)。
BHO可能会接收到很多事件,但我们只需要响应我们感兴趣的那一部分。
首先在EyeOnIE.h中增加该函数的声明,在EyeOnIE.cpp的实现中,笔者试着响应浏览器浏览一个地址之前发出的事件DISPID_BEFORENAVIGATE2,以此来实现简单的网址过滤功能,代码参考如下:
STDMETHODIMP CEyeOnIE::Invoke(DISPID dispidMember,REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams,
VARIANT * pvarResult,EXCEPINFO * pexcepinfo,
UINT * puArgErr)
{
USES_CONVERSION;
if (!pDispParams)
return E_INVALIDARG;
switch (dispidMember)
{
//
// The parameters for this DISPID are as follows:
// [0]: Cancel flag - VT_BYREF|VT_BOOL
// [1]: HTTP headers - VT_BYREF|VT_VARIANT
// [2]: Address of HTTP POST data - VT_BYREF|VT_VARIANT
// [3]: Target frame name - VT_BYREF|VT_VARIANT
// [4]: Option flags - VT_BYREF|VT_VARIANT
// [5]: URL to navigate to - VT_BYREF|VT_VARIANT
// [6]: An object that evaluates to the top-level or frame
// WebBrowser object corresponding to the event.
//
case DISPID_BEFORENAVIGATE2:
{
LPOLESTR lpURL = NULL;
mWebBrowser2->get_LocationURL(&lpURL);
char * strurl;
if (pDispParams->cArgs >= 5 && pDispParams->rgvarg[5].vt == (VT_BYREF|VT_VARIANT))
{
CComVariant varURL(*pDispParams->rgvarg[5].pvarVal);
varURL.ChangeType(VT_BSTR);
strurl = OLE2A(varURL.bstrVal);
}
if (strstr(strurl, ""))
{
*pDispParams->rgvarg[0].pboolVal = TRUE;
::MessageBox(NULL, _T("该网页已被禁止!"),_T("Warning"),MB_ICONSTOP);
return S_OK;
break;
}
case DISPID_NAVIGATECOMPLETE2:
break;
case DISPID_DOCUMENTCOMPLETE:
break;
case DISPID_DOWNLOADBEGIN:
break;
case DISPID_DOWNLOADCOMPLETE:
break;
case DISPID_NEWWINDOW2:
break;
case DISPID_QUIT:
RegisterEventHandler(FALSE);
break;
default:
break;
}
return S_OK;
}
我们看到,当用户浏览的新地址包含""字符的时候,浏览器就会弹出一个警告对话框,并且停止进一步的动作。
另外值得注意的是,在DISPID_QUIT事件(浏览器将要退出)的响应中,我们将BHO事件处理器进行了注销。
第四步,因为BHO可能会被文件浏览器加载。
如果我们不想这样,我们就要在DllMain中对加载者进行判断,参考如下:
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
// Check who''s loading us.
// If it''s Explorer then "no thanks" and exit...
TCHAR pszLoader[MAX_PATH];
GetModuleFileName(NULL, pszLoader, MAX_PATH);
_tcslwr(pszLoader);
if (_tcsstr(pszLoader, _T("explorer.exe")))
return FALSE;
_Module.Init(ObjectMap, hInstance, &LIBID_BHOPLUGINLib); DisableThreadLibraryCalls(hInstance);
else if (dwReason == DLL_PROCESS_DETACH)
_Module.T erm();
return TRUE; // ok
}
最后,别忘了修改注册表文件,追加BHO的注册信息。
在EyeOnIE.rgs文件的下面增加如下代码:
HKLM
{
SOFTWARE
{
Microsoft
{
Windows
{
CurrentVersion
{
Explorer
{
''Browser Helper Objects''
{
{6E28339B-7A2A-47B6-AEB2-46BA53782379}
}
}
}
}
}
}
}
注意,{6E28339B-7A2A-47B6-AEB2-46BA53782379}是笔者这个BHO的CLSID,如果你自己开发BHO,这里应该正确填写你的CLSID。
好了,一个简单的BHO开发完成了。
(可以到本人的个人主页 下载实例源代码。
)BHO插件可以实现的功能还有很多,比如网页内容分析、IE界面定制等等。
作为总结,笔者还要提醒读者一点的是,如果不想让BHO起作用了,可以注销该插件,如下格式:regsvr32 /u yourpath\yourbho.dll,或者直接在注册表中将“Browser Helper Objects”目录下注册的CLSID删掉。
正文完
如何使用BHO定制你的Internet Explorer浏览器
原文:微软公司Dino Esposito
编译:朱先中
原文出处:Browser Helper Objects: The Browser the Way You Want It
一、简介
有时,你可能需要一个定制版本的浏览器。
在这种情况下,你可以自由地把一些新颖但又不标准的特征增加到一个浏览器上。
结果,你最终有的只是一个新但不标准的浏览器。
W eb浏览器控件只是浏览器的分析引擎。
这意味着仍然存在若干的与用户接口相关的工作等待你做――增加一个地址栏,工具栏,历史记录,状态栏,频道栏和收藏夹等。
如此,要产生一个定制的浏览器,你可以进行两种类型的编程――一种象微软把Web浏览器控件转变成一个功能齐全的浏览器如Internet Explorer;一种是在现有的基础上加一些新的功能。
如果有一个直接的方法定制现有的Internet Explorer该多好?BHO(Browser Helper Objects,我译为"浏览器帮助者对象",以下皆简称BHO)正是用来实现此目的的。
二、关于软件定制
以前,定制一个软件的行为主要是通过子类化方法实现的。
通过这种办法,你可以改变一个窗口的外表与行为。
子类化虽然被认为是一种有点暴力方式――受害者根本不知道发生的事情――但它还是长时间以来的唯一的选择。
随着微软Win32 API的到来,进程间子类化不再被鼓励使用并愈发变得困难起来。
当然,如果你是勇敢的--指针从未吓倒你,而最重要的是,如果你已经游刃于系统钩子之间,你可能觉得这一问题太简单了。
但是情形并不总是这样。
暂放下这点不管,问题在于每一个进程运行在自己的地址空间中,而且打破进程边界略微有些不正确性。
另一方面,你可能需要对定制进行更好的管理。
更经常情况下,定制可能是程序本身强烈要求实现的。
在后者情况下,已安装的软件只需在既定的磁盘位置查询另外的组件模块,然后装载、设定初值,最后让它们自由地按照既定的设计工作。
这正是Internet Explorer浏览器和它的BHO所要实现的。
三、什么是BHO?
从某种观点看,Internet Explorer同普通的Win32程序没有什么两样。
借助于BH O,你可以写一个进程内COM对象,这个对象在每次启动时都要加载。
这样的对象会在与浏览器相同的上下文中运行,并能对可用的窗口和模块执行任何行动。
例如,一个BHO能够探测到典型的事件,如GoBack、GoForward、DocumentComplete等;另外BHO 能够存取浏览器的菜单与工具栏并能做出修改,还能够产生新窗口来显示当前网页的一些额外信息,还能够安装钩子以监控一些消息和动作。
简而言之,BHO的工作如我们打入浏览器领地的一位间谍(注意这是微软允许的合法工作)。
在进一步了解BHO细节之前,有几点我需要进一步阐述。
首先,BHO对象依托于浏览器主窗口。
实际上,这意味着一旦一个浏览器窗口产生,一个新的BHO对象实例就要生成。
任何BHO对象与浏览器实例的生命周期是一致的。
其次,BHO仅存在于Internet Explorer 4.0及以后版本中。
如果你在使用Microsoft Windows? 98,Windows 2000,Windows 95,or Wi ndows NT版本4.0 操作系统的话,也就一块运行了活动桌面外壳4.71,BHO也被Wi ndows资源管理器所支持。
BHO是一个COM进程内服务,注册于注册表中某一键下。
在启动时,Internet Explorer查询那个键并把该键下的所有对象预以加载。
Internet Explorer浏览器初始化这一对象并要求某一接口功能。
如果发现这一接口,
Internet Explorer使用其提供的方法传递IUnknown 指针到BHO对象。
见图一:
图一ie浏览器如何装入和初始化BHO对象,BHO场所(site)是用于实现通信的COM 接口
浏览器可能在注册表中发现一系列的CLSID,并由此为每个CLSID建立一个进程中实例。
结果是,这些对象被装载至浏览器上下文中并运行起来,好象它们是本地组件一样。
但是,由于Internet Explorer的COM特性,即使被装入到它的进程空间中于事(你的野心实现)也不一定会有多大帮助。
用另一说法,BHO的确能够做许多潜在的有用的事情,如子类化组成窗口或者安装线程局部钩子,但是它确实远离浏览器的核心活动。
为了钩住浏览器的事件或者自动化浏览器,BHO需要建立一个私有的基于COM的通讯通道。
为此,该BHO应该实现一个称为IObjectWithSite的接口。
事实上,通过接口IobjectWithSi te,Internet Explorer 可以传递它的IUnknown 接口。
BHO反过来能够存储该接口并进一步查询更专门的接口,如IWebBrowser2、IDispatch和IConnectionPointCon tainer。
另外一种分析BHO对象的途径与Internet Explorer外壳扩展有关。
我们知道,一个WINDOWS外壳扩展即是一个进程内的COM服务器,它在Windows资源管理器执行某种动作时装入内存――如显示上下文菜单。
通过建立一个实现几个COM接口的COM模块,你就给上下文菜单加上一些项并能预以正确处理。
一个外壳扩展必须以Windows 资源管理器能够发现的方法注册。
一个BHO对象遵循同样的模式――唯一的改变在于要实现的接口。
然而,尽管实现方式有所不同,外壳扩展与BHO 仍有许多共同的特点。
如下表一:
表一外壳扩展与BHO相近特性比较
特性外壳扩展BHO对象
Internet Explorer(和外壳4.17及以上版本的加载者Windows资源管理器
Windows资源管理器)
击活动作在某类文档上的用户动作(即单击右键)打开浏览器窗口
何时卸载参考计数达到0的几秒之后导致它加载的窗口关闭时
实现形式COM进程中DLL COM 进程中DLL
注册需求常常是为一个COM服务器设置的入口处,另
加的入口依赖于外壳类型及它要应用至的文
档类型
常常是为一个COM服务器设置的入口处,另加
一个把它申请为BHO的注册入口
接口需求依赖于外壳扩展的类型IObjectWithSite 如果你对SHELL扩展编程有兴趣的话,可以参考MSDN有关资料。
四、BHO的生存周期
前面已经说过,BHO不仅仅为Internet Explorer所支持。
如果你在使用外壳4.71或者更高版本,你的BHO对象也会被Windows资源管理器所加载。
下表二展示了我们可以使用的不同版本的外壳产品情况,Windows外壳版本号存于库文件shell32.dll中。
表二不同版本的Windows外壳对于BHO的支持情况
外壳版本安装的产品BHO的支持情况
4.00
Windows 95,Windows NT 4.0 带或不带
Internet Explorer 4.0 或更老版本。
注意没有
安装外壳更新
Internet Explorer 4.0
4.71
Windows 95,Windows NT 4.0 带Internet
Explorer 4.0 和活动桌面外壳更新Internet Explorer 与Windows 资源管
理器
4.72 Windows 98
Internet Explorer与Windows 资源管
理器
5.00 Windows 2000
Internet Explorer与Windows 资源管
理器
BHO对象随着浏览器主窗口的显示而装入,随着浏览器主窗口的销毁而缷载。
如果你打开多个浏览器窗口,多个BHO实例也一同产生。
无论浏览器以什么样的命令行启动,BHO对象都被加载。
举例来说,即使你只是想要见到特定的HTML 页或一个给定的文件夹,BHO对象也被加载。
一般地,当explorer.e xe 或iexplore.exe 运行的时候,BHO都要被考虑在内。
如果你设置了"Open each fo lder in its own window"(对每一个文件夹以一个独立窗口打开)文件夹选项,那么你每次打开一个文件夹,BHO对象都要被加载。
见图二。
图二经过这样设置,你每次打开一个文件夹时,执行一个独立的explorer.exe实例,并装入已注册的BHO对象。
但是注意,这种情形仅适于当你从桌面上的"我的电脑"图标中打开文件夹的情况。
在这种情况下,每次你移到另外一个文件夹时外壳都要调用explorer.exe。
这种情况在你同时用两个窗格进行浏览时是不会发生的。
事实上,当你改变文件夹时,外壳是不会启动浏览器的新的实例的而仅是简单创建嵌入视图对象的另外一个实例。
奇怪的是,如果你在地址栏中输入一个新的名字来改变文件夹时,在同一个窗口中同样可以达到浏览之目的,无论Win dows资源管理器视图是单个的还是双视图形式。
对于Internet Explorer的情形,事情要更简单一些。
只有你显式地多次运行iexplo re.exe浏览器时,你才有多个Internet Explorer的拷贝。
当你从Internet Explorer 中打开新的窗口时,每一个窗口在一个新的线程中被复制而不是创建一个新的进程,因此也就不需要重新载入BHO对象。
首先,BHO最有趣的地方是,它是极度动态的。
每次Windows资源管理器或者Int ernet Explorer打开,装载器从注册表中读取已安装的BHO对象的CLSID然后处理它们。
如果你在打开的浏览器多个实例中间编辑注册表的话,你可以随着多个浏览器拷贝的载入而装入多个不同的BHO。
这就是说,如果你选择从头创建一个新的属于自己的浏览器,那么你可以把它内嵌在一个Visual Basic或者MFC框架窗口中。
同时你有相当的机会来灵活安排浏览程序。
如果它们能满足你的需要的话,你可以依赖于Internet Explorer的强大的功能并且加上你想要的尽可能多的插件。
五、关于IObjectWithSite接口
从一个高起点来看,BHO即是一个DLL,它能够依附于Internet Explorer浏览器的一个新建的实例,在某些情况下也适用于Windows资源管理器。
一般地,一个场所(site)是一个中间对象,它位于容器对象与被包容对象之间。
通过它,容器对象管理被包容对象的内容,也因此使得对象的内部功能可用。
为此,容器方要实现接口IoleClientSite,被包容对象要实现接口IOleObject 。
通过调用IOleObject提供的方法,容器对象使得被包容对象清楚地了解其HOST的环境。
一旦容器对象成为Internet Explorer(或是具有WEB能力的Windows资源管理器),被包容对象只需实现一个轻型的IObjectWithSite接口。
该接口提供了以下方法:
表三IObjectWithSite定义
方法描述
HRESULT SetSite(IUnknown* pUnkSite)
接收ie浏览器的IUnknown指针。
典型实现是保存
该指针以备将来使用。
.
HRESULT GetSite(REFIID riid,void**
ppvSite) 从通过SetSite()方法设置的场所中接收并返回指定的接口,典型实现是查询前面保存的接口指针以进一
步取得指定的接口。
对BHO 的唯一严格的要求正在于必须实现这一个接口。
注意你应该避免在调用以上任何一个函数时返回E_NOTIMPL 。
要么你不实现这一接口,要么应保证在调用这些方法时进行正确地编码。
六、构造自己的BHO对象
一个BHO对象就是一个进程中服务器DLL,选用ATL创建它是再恰当不过的了。
我们选择ATL的另外一个原因是因为它已经提供了缺省的而且提供了IObjectWithSite接口的足够好的实现。
另外,在ATL COM 向导本地支持的已定义好的对象类型当中,有一个,就是Internet Explorer对象,这正是一个BHO应该具有的类型。
一个ATL Inter net Explorer 对象,事实上是一个简单对象――也就是说,是一个支持IUnknown和自注册,还有接口IObjectWithSite的COM 服务器。
如果你在ATL工程中添加一个这样的对象,并调用相应的类CViewSource,你将从向导中得到下列代码:
class ATL_NO_VTABLE CViewSource :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CViewSource, &CLSID_ViewSource>,
public IObjectWithSiteImpl<CViewSource>,
public IDispatchImpl<IViewSource, &IID_IViewSource,
&LIBID_HTMLEDITLib>
正如你所见,向导已经使类从接口IObjectWithSiteImpl继承,这是一个ATL模板类,它提供了接口I ObjectWithSite的基本实现。
一般情况下,没有必要重载成员函数GetSite()。
取而代之的是,SetSit e() 实现代码经常需要加以定制。
ATL实际上仅仅把一个IUnknown接口指针存储在成员变量m_spUn kSite中。
在文章的剩余部分,我将讨论一个BHO 的相当复杂而丰富的例子。
该BHO对象将依附于Internet Explorer,并显示一个文本框来显示当前正浏览的网页源码。
该代码窗口将随着你改变网页而自动更新,如果浏览器显示的不是一个HTML网页时,它将变灰。
你对于原始HTML代码的任何改动立即反映在浏览器中。
HTML (DHTML)使得这一看似魔术般的实现成为可能。
该代码窗口可被隐藏和通过按动热键重现。
在可见情况下,它与Internet Explorer共享整个桌面空间,见图三。
图三BHO对象在使用中。
它依附于Internet Explorer,并显示一个窗口来显示当前正浏览的网页源码。
还允许你源码进行修改。
本例子的关键点在于存取Internet Explorer的浏览机制,其实它只不过是WebBrowser控件的一个实例而已。
这个例子可以分解为以下五步来实现:
1.探测谁在装入这个对象,是Internet Explorer还是Windows资源管理器;
2.获取接口IWebBrowser2以实现Web浏览器对象;
3.捕捉Web浏览器的特定事件;
4.存取当前文档对象,确定它是一份HTML类型的文件;
5.管理对话框窗口以实现HTML源码的显示;
第一个步骤是在DllMain()中完成的。
SetSite()是取得指向WebBrowser对象指针的适当位置。
请详细分析以下步骤。
七、探测谁在调用这个对象
如前所述,一个BHO对象会被Internet Explorer或者Windows资源管理器(前提:外壳版本4.71或者更高)所加载。
所以我专门设计了一个BHO来处理HTML网页,因此这个BHO与资源管理器毫无关系。
如果一个Dll不想被调用者一起加载,只需在DllMai n()中实现了探明谁在调用该对象后返回FALSE即可。
参看下面代码:
if (dwReason == DLL_PROCESS_ATTACH)
{
TCHAR pszLoader[MAX_PATH];
//返回调用者模块的名称,第一个参数应为NULL,详见msdn。
GetModuleFileName(NULL, pszLoader, MAX_PATH);
_tcslwr(pszLoader);
if (_tcsstr(pszLoader, _T("explorer.exe")))
return FALSE;
}
一旦知道了当前进程是Windows资源管理器,可立即退出。
注意,再多加一些条件语句是危险的!事实上,另外一些进程试图装入该DLL时将被放弃。
如果你做
另外一个试验,比方说针对Internet Explorer的执行文件iexplorer.exe,这时第一个受害者就是regs vr32.exe(该程序用于自动注册对象)。
if (!_tcsstr(pszLoader, _T("iexplore.exe")))
你不能够再次注册该DLL库了。
事实上,当regsvr32.exe 试图装入DLL以激活函数DllRegisterSer ver()时,该调用将被放弃。
八、与Web浏览器取得联系
SetSite()方法正是BHO对象被初始化的地方,此外,在这个方法中你可以执行所有的仅仅允许发生一次的任务。
当你用Internet Explorer打开一个URL时,你应该等待一系列的事件以确保要求的文档已完全下载并被初始化。
唯有在此时,你才可以通过对象模型暴露的接口(如果存在的话)存取文档内容。
这就是说你要取得一系列的指针。
第一个就是指向IWebBrowser2(该接口用来生成WebBrowser对象)的指针。
第二个指针与事件有关。
该模块必须作为一个浏览器的事件侦听器来实现,目的是为接收下载以及与文档相关的事件。
下面用ATL灵敏指针加以封装:
CComQIPtr< IWebBrowser2, &IID_IWebBrowser2> m_spWebBrowser2; CComQIPtr<IConnectionPointContainer,
&IID_IConnectionPointContainer> m_spCPC;
源代码部分如下所示:
HRESULT CViewSource::SetSite(IUnknown *pUnkSite)
{
// 检索并存储 IWebBrowser2 指针
m_spWebBrowser2 = pUnkSite;
if (m_spWebBrowser2 == NULL)
return E_INVALIDARG;
//检索并存储 IConnectionPointerContainer指针
m_spCPC = m_spWebBrowser2;
if (m_spCPC == NULL)
return E_POINTER;
//检索并存储浏览器的句柄HWND. 并且安装一个键盘钩子备后用
RetrieveBrowserWindow();
// 为接受事件通知连接到容器
return Connect();
}
为了取得IWebBrowser2接口指针,你可以进行查询。
当然也可以在事件刚刚发生时查询IConnection PointContainer。
这里,SetSite()检索了浏览器的句柄HWND,并且在当前线程中安装了一个键盘钩子。
HWND用于后面Internet Explorer窗口的移动或尺寸调整。
这里的钩子用来实现热键功能,用户可以按动热键来显示/隐藏代码窗口。
九、从Internet Explorer浏览器取得事件
当你导向一个新的URL时,浏览器最需要完成的是两种事件:下载文档并为之准备HOST环境。
也就是说,它必须初始化某对象并使该对象从外部可以利用。
针对不同的文档类型,或者装入一个已注册的Microsoft ActiveX? 服务器来处理该文档(如Word对于.doc文件的处理)或者初始化一些内部组件来分析文档内容并生成和显示该文档。
对于HTML网页就是这样,其内容由于DHTML对象作用而变得可用。
当文档全部下载结束,DownloadComplete事件被激活。
这并不是说,这样利用对象模型就可以安全地管理文档的内容了。
事实上,DocumentComplete 事件仅指明一切已经结束,文档已准备好了(注意D ocumentComplete事件仅在你第一次存取URL时到达,如果你执行了刷新动作,你仅仅收到一个Doc umentComplete事件)。
为了截获浏览器发出的事件,BHO需要通过IConnectionPoint 接口连接到浏览器上并且实现传递接口IDispatch指针以处理各种事件。
现在利用前面取得的IConnectionPointContainer指针来调用F indConnectionPoint方法――它返回一个指针指向连接点对象(正是通过这个连接点对象来取得要求的外向接口,此时是DIID_DWebBrowserEvent2)。
下列代码显示了连接点的发生情况:HRESULT CViewSource::Connect(void)
{
HRESULT hr;
CComPtr<IConnectionPoint> spCP;
//为Web浏览器事件而接收(receive)连接点
hr = m_spCPC->FindConnectionPoint(DIID_DWebBrowserEvent2, &spCP); if (FAILED(hr))
return hr;
// 把事件处理器传递到容器。
每次事件发生容器都将激活我们实现的IDispa tch接口上的相应的函数。
hr = spCP->Advise( reinterpret_cast<IDispatch*>(this), &m_dwCookie); return hr;
}
通过调用接口IConnectionPoint的Advise() 方法,BHO告诉浏览器它对它产生的事件很感兴趣。
由
于COM事件处理机制,所有这些意味着BHO把IDispatch接口指针提供给浏览器。
浏览器将回调IDis patch接口的Invoke() 方法,以事件的ID值作为第一参数:
HRESULT CViewSource::Invoke(DISPID dispidMember, REFIID riid,
LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
if (dispidMember == DISPID_DOCUMENTCOMPLETE) {
OnDocumentComplete();
m_bDocumentCompleted = true;
}
:
}
切记,当事件不再需要时,应该使之与浏览器分离。
如果你忘记了做这件事情,BHO对象将被锁定,即使
在你关闭浏览器窗口之后。
很明显,实现分离的最佳时机是收到事件OnQuit时。
十、存取文档对象
此时,该BHO已经有一个参照指向Internet Explorer的Web浏览器控件并被连接到浏览器控件
以接收所有它产生的事件。
当网页被全部下载并正确初始化后,我们就可以通过DHTML文档模型存取它。
Web浏览器的文档属性返回一个指向文档对象的IDispatch接口的指针:
CComPtr<IDispatch> pDisp;
HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);
get_Document() 方法取得的仅仅是一个接口指针。
我们要进一步确定在IDispatch 指针背后存在一个HTML文档对象。
用VB实现的话,可以用下面代码:
Dim doc As Object
Set doc = WebBrowser1.Document
If TypeName(doc)="HTMLDocument" Then
\'\' 获取文档内容并予以显示
Else
\'\' Disable the display dialog
End If
现在要了解一下get_Document()返回的IDispatch指针。
Internet Explorer不仅仅是一个HTML 浏览器,而且还是一个ActiveX文档容器。
这样一来,难以保证当前浏览对象就是一个HTML文档。
不过办法还是有的――你想,如果IDispatch指针真正指向一个HTML文档,查询IHTMLDocument2 接口一定成功。
IHTMLDocument2接口包装了DHTML对象模型用来展现HTML页面的所有功能。
下面代码实现这些功能:
CComPtr<IDispatch> pDisp;
HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);
CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> spHTML;
spHTML = pDisp;
if (spHTML) {
// 获取文档内容并予以显示
}
else {
// disable the Code Window controls
}
如果IHTMLDocument2接口查询失败,spHTML指针将是NULL。
现在考虑如何获得当前显示窗口的源代码。
正如一个HTML页把它所有的内容封装在标签<BODY>中,DHTML对象模型要求你取得一个指向Body对象的指针:
CComPtr<IHTMLElement> m_pBody;
hr = spHTML->get_body(&m_pBody);
奇怪的是,DHTML对象模型不让你取得标签<BODY>之前的原始内容,如<HEAD>。
其内容被处理并存于一些属性中,但你还是不能从HTML原始文件中提取这部分的RAW文本。
这过,仅从BODY部分取。