另外,远程钩子和局部钩子的程序结构也是不同的。当安装了一个局部钩子时,每当指定的事件发生,Windows就可以调用进程中的钩子函数;但是若安装的是远程钩子,系统不能从其他进程的地址空间中调用钩子函数,因为两个进程的地址空间是隔离的,由于系统中只有DLL程序是可以插入到其他进程的地址空间中去的,所以远程钩子的钩子函数必须位于一个动态链接库中,而且必须是共享数据段的动态链接库(因为写远程钩子要用到动态链接库,所以本书中将两部分内容合在一章中介绍)。
但是也有两个例外:日志记录钩子和日志回放钩子虽然属于远程钩子,但是它们的钩子函数却可以放在安装钩子的程序中,并不需要单独放在一个动态链接库中。Microsoft并没有说明为什么有这样的例外,笔者认为其中的原因是这两个钩子是用来监控比较底层的硬件事件的,所以钩子函数的调用并不是从其他进程的地址空间中发起的,而是从Windows内部发起的,所以不存在不同进程之间地址空间隔离的问题。(猜想而已,如果读者有明确的资料请告知笔者。)
下面的11.2.2节以键盘钩子为例来说明系统范围远程钩子的安装和使用,局部钩子的使用步骤与之类似,只不过不必将钩子函数放在动态链接库中而已,使用起来更加简单,读者可以举一反三自己尝试一下。11.2.3节中演示日志钩子的使用方法。
11.2.2
远程钩子的安装和使用
1. 钩子程序的结构
钩子程序一般包括3个功能模块:
(1)主程序——用来实现界面或者其他功能。
(2)钩子回调函数——用来接收系统发过来的消息。
(3)钩子的安装和卸载程序。
对于局部钩子来说,这些模块可以处在同一个可执行文件中。而对于远程钩子来说,第2部分必须放在一个动态链接库中,第3部分虽然没有要求,但一般也放在动态链接库中,这是因为钩子创建以后得到一个钩子句柄,这个句柄要在钩子回调函数中以及卸载钩子的时候用到,如果把这部分代码放在主程序中的话,还需要创建一个函数将它传回给动态链接库,所以还不如直接放到库中。
所附光盘的Chapter11\KeyHook目录中的例子采用的就是这样的结构。目录中包括两部分文件:HookDll.asm和HookDll.def文件用来生成动态链接库;Main.asm和Main.rc是主程序部分。程序用一个系统范围的远程钩子来实现监视所有键盘输入的功能。由于安装钩子回调函数的动态链接库要求是共享数据段的,所以请读者注意Makefile中dll文件的链接选项,它使用了/section:.bss,S选项。
NAME = Main
DLL = Hookdll
ML_FLAG = /c /coff
LINK_FLAG = /subsystem:windows
DLL_LINK_FLAG = /subsystem:windows /section:.bss,S
$(DLL).dll $(NAME).exe:
$(DLL).dll: $(DLL).obj $(DLL).def
Link
$(DLL_LINK_FLAG) /Def:$(DLL).def /Dll $(DLL).obj
$(NAME).exe: $(NAME).obj $(NAME).res
Link
$(LINK_FLAG) $(NAME).obj $(NAME).res
.asm.obj:
ml $(ML_FLAG) $<
.rc.res:
rc $<
clean:
del *.obj
del *.res
del *.exp
del *.lib