浏览器辅助对象BHO(Browser Helper Object)是一种ATL COM对象,由IE在启动时自动加载。BHO运行在IE的地址空间内,能对IE中可访问对象的各类事件消息进行监听并作出相应处理。因此,当IE已成为进入网络世界的主要大门时,BHO自然变得炙手可热,不管是扩展IE功能的辅助软件还是令人深恶痛绝的流氓软件,都对BHO青睐有加。那么,用于扩展IE功能的BHO插件到底如何开发呢?下面以开发一个过滤特定网址的BHO插件为例进行说明。
监听浏览器事件
在Delphi 7中,新建ActiveX Library项目MyBHO。再在项目中新建COM Object,命名为MyIEBHO。作为特殊的COM对象,BHO必须实现同浏览器通讯的两个接口IObjectWithSite和Idispatch,其中IobjectWithSite接口用来挂钩和监控浏览器事件。
IE在加载BHO时,会将自己的IUnknown接口用pUnkSite参数传给BHO。通过对pUnkSite的解析即可获得浏览器接口IWebBrowser2。而获得IWebBrowser2后,又可得到浏览器事件连接点接口。再使用该接口的Advise方法,便可实现对浏览器事件的监听。IobjectWithSite接口包含GetSite和SetSite方法,其中由SetSite实现IobjectWithSite接口的主要功能。
function TMyIEBHO.SetSite(const pUnkSite:IUnknown):HResult;
var
cmdTarget:IOleCommandTarget;
Sp:IServiceProvider;
begin
if(Assigned(pUnkSite))then
begin
cmdTarget:=(pUnkSite as IOleCommandTarget);
Sp:=(CmdTarget as IServiceProvider);
if(Assigned(Sp))then //获得IE的WebBrowser接口,
Sp.QueryService(IWebBrowserApp,IWebBrowser2,IEThis);
if(Assigned(IEThis))then
begin
IEThis.QueryInterface(IConnectionPointContainer,CPC); //查找连接点
CPC.FindConnectionPoint(DWEBBrowserEvents2,CP);
CP.Advise(Self,Cookie); //用Advise方法实现监听
end;
end;
Result:=S_OK;
end;
function TMyIEBHO.GetSite(const riid:TIID;out site:IUnknown):HResult;
begin
if(Assigned(IEThis))then
Result:=IEThis.QueryInterface(riid,site)
else Result:=E_FAIL;
end;
对浏览器事件进行处理
BHO的另一接口Idispatch主要用来对浏览器事件进行处理。每当浏览器有事件发生时,IE就会调用IDispatch接口的Invoke方法通知事件类型及参数,并请求BHO对事件进行处理。因此在Idispatch接口中最重要的是Invoke方法,BHO的功能基本上都在Invoke方法中实现。至于Idispatch的其它方法GetTypeInfoCount、GetTypeInfo和GetIDsOfNames,则只需返回E_NOTIMPL即可。
浏览器事件在DWebEvents2接口中定义,每种事件类型都用特定的dspid数字符号来标识,如 DownloadComplete事件为dispid 104,BeforeNavigate2为dispid 250,OnQuit为dispid 253。对过滤特定网址任务而言,只需要截获BeforeNavigate2事件并对其作相应处理就可以了。另外,在关闭浏览器时还要对OnQuit事件作处理,以断开对浏览器事件的监听。
function TMyIEBHO.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
type
POleVariant=^OleVariant;
var
dps:TDispParams absolute Params;
bHasParams:Boolean;
pDispIDs:PDispIDList;
iDispIDsSize:Integer;
begin
Result:=DISP_E_MEMBERNOTFOUND;
pDispIDs:=nil;
iDispIDsSize:=0;
bHasParams:=(dps.cArgs>0);
if(bHasParams)then
begin
iDispIDsSize:=dps.cArgs*SizeOf(TDispID);
GetMem(pDispIDs,iDispIDsSize);
end;
try
if(bHasParams)then BuildPositionalDispIDs(pDispIDs,dps);
case DispID of
104:begin
Result:=S_OK;
end;
250:begin
DoBeforeNavigate2(IDispatch(dps.rgvarg^[pDispIDs^[0]].dispVal),
POleVariant(dps.rgvarg^[pDispIDs^[1]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[2]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[3]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[4]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[5]].pvarVal)^,
dps.rgvarg^[pDispIDs^[6]].pbool^);
Result:=S_OK;
end;
253:begin
CP.Unadvise(Cookie);
Result:=S_OK;
end;
end;
finally
if(bHasParams)then
FreeMem(pDispIDs,iDispIDsSize);
end;
end;
BeforeNavigate2过程中包含了过滤特定网址的处理逻辑。它从MyIEBHO.txt文件中读取需要过滤的网址,然后同浏览器当前地址作比对,如果是,则直接转向网易网站。
procedure DoBeforeNavigate2(const pDisp:IDispatch;var URL:OleVariant;var Flags:OleVariant;var TargetFrameName:OleVariant;var PostData:OleVariant; var Headers:OleVariant;var Cancel:WordBool);
var
s:String;
URLFile:TextFile;
begin
Assign(URLFile, ’c:\MyIEBHO.txt’);
Reset(URLFile);
Try
while not Eof(URLFile) do
begin
ReadLn(URLFile, s);
if (Trim(URL)=Trim(s)) then
begin
Cancel:=True;
URL:=’http://www.163.com’;
(pDisp as IWebbrowser2).Navigate2(URL,Flags,TargetFrameName,PostData,Headers);
end;
end;
Finally
Close(URLFile);
end;
end;
注册BHO插件
同所有COM对象一样,BHO也需要使用regsvr32进行注册或卸载。此外,BHO还必须将自己的Guid字符串关键字添加到注册表HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\下,这样浏览器才能正确加载与之对应的BHO插件。该键值既可手工创建,也可以在BHO中用注册表对象直接创建。
procedure TIEAdvBHOFactory.UpdateRegistry(Register: Boolean);
begin
inherited;
if Register then
CreateRegKeyValue(HKEY_LOCAL_MACHINE, ’Software\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\’ + GuidToString(ClassID), ’’, ’’)
else
DeleteRegKeyValue(HKEY_LOCAL_MACHINE, ’Software\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\’ + GuidToString(ClassID), ’’);
end;
将代码生成为MyBHO.dll文件,然后运行“regsrv32 MyBHO.dll”进行注册,或运行“regsrv32 MyBHO.dll /u”进行注销。另外,别忘了在C:\新建MyIEBHO.txt文件,并在里面输入需要过滤的网址,否则可能导致IE和资源浏览器出错。注册成功后,重新运行IE,在地址栏中完整地输入待过滤网址,即可发现IE直接转向了网易网站。
BHO插件开发总结
在Windows程序开发中,COM组件技术始终是令程序员挠头的部分。不是COM的倡导者微软有意难为大家,而实在是COM太过复杂。尽管Delphi对COM进行了很好的封装并提供了多种模板,但开发难度依然不小。而BHO在COM中属于较新的领域,加之资料厥如,因此对许多初涉BHO开发的程序员来说,一时不得其门而入也在情理之中。不过,从上面的简单示例可以看出,BHO开发基本上是模板化的,各种接口的实现差不多都是固定式样,照抄现成的代码就可以了,即使没有将代码完全弄懂也无大碍。而真正体现编程创意和功力的地方主要集中在事件的处理代码上,相对而言,这一部分又是BHO开发中最容易掌握的,因此只要掌握了BHO开发诀窍,就可以化难为易。由于BHO的接口实现代码可以完全封装起来,估计随着BHO开发热的高涨,各种BHO开发控件将陆续面市。到那时,BHO开发将不再被视为畏途,而IE的功能也会因BHO插件的繁荣而被大大拓展。
监听浏览器事件
在Delphi 7中,新建ActiveX Library项目MyBHO。再在项目中新建COM Object,命名为MyIEBHO。作为特殊的COM对象,BHO必须实现同浏览器通讯的两个接口IObjectWithSite和Idispatch,其中IobjectWithSite接口用来挂钩和监控浏览器事件。
IE在加载BHO时,会将自己的IUnknown接口用pUnkSite参数传给BHO。通过对pUnkSite的解析即可获得浏览器接口IWebBrowser2。而获得IWebBrowser2后,又可得到浏览器事件连接点接口。再使用该接口的Advise方法,便可实现对浏览器事件的监听。IobjectWithSite接口包含GetSite和SetSite方法,其中由SetSite实现IobjectWithSite接口的主要功能。
function TMyIEBHO.SetSite(const pUnkSite:IUnknown):HResult;
var
cmdTarget:IOleCommandTarget;
Sp:IServiceProvider;
begin
if(Assigned(pUnkSite))then
begin
cmdTarget:=(pUnkSite as IOleCommandTarget);
Sp:=(CmdTarget as IServiceProvider);
if(Assigned(Sp))then //获得IE的WebBrowser接口,
Sp.QueryService(IWebBrowserApp,IWebBrowser2,IEThis);
if(Assigned(IEThis))then
begin
IEThis.QueryInterface(IConnectionPointContainer,CPC); //查找连接点
CPC.FindConnectionPoint(DWEBBrowserEvents2,CP);
CP.Advise(Self,Cookie); //用Advise方法实现监听
end;
end;
Result:=S_OK;
end;
function TMyIEBHO.GetSite(const riid:TIID;out site:IUnknown):HResult;
begin
if(Assigned(IEThis))then
Result:=IEThis.QueryInterface(riid,site)
else Result:=E_FAIL;
end;
对浏览器事件进行处理
BHO的另一接口Idispatch主要用来对浏览器事件进行处理。每当浏览器有事件发生时,IE就会调用IDispatch接口的Invoke方法通知事件类型及参数,并请求BHO对事件进行处理。因此在Idispatch接口中最重要的是Invoke方法,BHO的功能基本上都在Invoke方法中实现。至于Idispatch的其它方法GetTypeInfoCount、GetTypeInfo和GetIDsOfNames,则只需返回E_NOTIMPL即可。
浏览器事件在DWebEvents2接口中定义,每种事件类型都用特定的dspid数字符号来标识,如 DownloadComplete事件为dispid 104,BeforeNavigate2为dispid 250,OnQuit为dispid 253。对过滤特定网址任务而言,只需要截获BeforeNavigate2事件并对其作相应处理就可以了。另外,在关闭浏览器时还要对OnQuit事件作处理,以断开对浏览器事件的监听。
function TMyIEBHO.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
type
POleVariant=^OleVariant;
var
dps:TDispParams absolute Params;
bHasParams:Boolean;
pDispIDs:PDispIDList;
iDispIDsSize:Integer;
begin
Result:=DISP_E_MEMBERNOTFOUND;
pDispIDs:=nil;
iDispIDsSize:=0;
bHasParams:=(dps.cArgs>0);
if(bHasParams)then
begin
iDispIDsSize:=dps.cArgs*SizeOf(TDispID);
GetMem(pDispIDs,iDispIDsSize);
end;
try
if(bHasParams)then BuildPositionalDispIDs(pDispIDs,dps);
case DispID of
104:begin
Result:=S_OK;
end;
250:begin
DoBeforeNavigate2(IDispatch(dps.rgvarg^[pDispIDs^[0]].dispVal),
POleVariant(dps.rgvarg^[pDispIDs^[1]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[2]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[3]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[4]].pvarVal)^,
POleVariant(dps.rgvarg^[pDispIDs^[5]].pvarVal)^,
dps.rgvarg^[pDispIDs^[6]].pbool^);
Result:=S_OK;
end;
253:begin
CP.Unadvise(Cookie);
Result:=S_OK;
end;
end;
finally
if(bHasParams)then
FreeMem(pDispIDs,iDispIDsSize);
end;
end;
BeforeNavigate2过程中包含了过滤特定网址的处理逻辑。它从MyIEBHO.txt文件中读取需要过滤的网址,然后同浏览器当前地址作比对,如果是,则直接转向网易网站。
procedure DoBeforeNavigate2(const pDisp:IDispatch;var URL:OleVariant;var Flags:OleVariant;var TargetFrameName:OleVariant;var PostData:OleVariant; var Headers:OleVariant;var Cancel:WordBool);
var
s:String;
URLFile:TextFile;
begin
Assign(URLFile, ’c:\MyIEBHO.txt’);
Reset(URLFile);
Try
while not Eof(URLFile) do
begin
ReadLn(URLFile, s);
if (Trim(URL)=Trim(s)) then
begin
Cancel:=True;
URL:=’http://www.163.com’;
(pDisp as IWebbrowser2).Navigate2(URL,Flags,TargetFrameName,PostData,Headers);
end;
end;
Finally
Close(URLFile);
end;
end;
注册BHO插件
同所有COM对象一样,BHO也需要使用regsvr32进行注册或卸载。此外,BHO还必须将自己的Guid字符串关键字添加到注册表HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\下,这样浏览器才能正确加载与之对应的BHO插件。该键值既可手工创建,也可以在BHO中用注册表对象直接创建。
procedure TIEAdvBHOFactory.UpdateRegistry(Register: Boolean);
begin
inherited;
if Register then
CreateRegKeyValue(HKEY_LOCAL_MACHINE, ’Software\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\’ + GuidToString(ClassID), ’’, ’’)
else
DeleteRegKeyValue(HKEY_LOCAL_MACHINE, ’Software\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\’ + GuidToString(ClassID), ’’);
end;
将代码生成为MyBHO.dll文件,然后运行“regsrv32 MyBHO.dll”进行注册,或运行“regsrv32 MyBHO.dll /u”进行注销。另外,别忘了在C:\新建MyIEBHO.txt文件,并在里面输入需要过滤的网址,否则可能导致IE和资源浏览器出错。注册成功后,重新运行IE,在地址栏中完整地输入待过滤网址,即可发现IE直接转向了网易网站。
BHO插件开发总结
在Windows程序开发中,COM组件技术始终是令程序员挠头的部分。不是COM的倡导者微软有意难为大家,而实在是COM太过复杂。尽管Delphi对COM进行了很好的封装并提供了多种模板,但开发难度依然不小。而BHO在COM中属于较新的领域,加之资料厥如,因此对许多初涉BHO开发的程序员来说,一时不得其门而入也在情理之中。不过,从上面的简单示例可以看出,BHO开发基本上是模板化的,各种接口的实现差不多都是固定式样,照抄现成的代码就可以了,即使没有将代码完全弄懂也无大碍。而真正体现编程创意和功力的地方主要集中在事件的处理代码上,相对而言,这一部分又是BHO开发中最容易掌握的,因此只要掌握了BHO开发诀窍,就可以化难为易。由于BHO的接口实现代码可以完全封装起来,估计随着BHO开发热的高涨,各种BHO开发控件将陆续面市。到那时,BHO开发将不再被视为畏途,而IE的功能也会因BHO插件的繁荣而被大大拓展。