WIN32汇编语言教程:第11章 动态链接库和钩子 · 11.2 Windows钩子(4)

对于每个击键动作,钩子回调函数会在键按下和释放的时候被调用两次,只需根据 lParam的位31中的标志来记录一次,否则得到的是重复信息。

另外,回调函数收到的参数是以按键的扫描码和虚拟码表示的,在送给主窗口前需要将它转换成我们认识的ASCII码,但虚拟码或扫描码和ASCII码之间的对应关系并没有规律,必须进行查表操作才能转换。如果在程序中自己转换的话,需要一个键码对应表和查表程序。

Windows中现成的函数ToAscii可以完成这个功能并自动辨认按键的按下或释放动作。代码如下。

HookProc       proc       _dwCode,_wParam,_lParam
            local  @szKeyState[256]:byte
 
              invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam
             invoke GetKeyboardState,addr @szKeyState
            invoke GetKeyState,VK_SHIFT
              mov    @szKeyState + VK_SHIFT,al
            mov    ecx,_lParam
              shr    ecx,16
            invoke ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0
            mov    byte ptr szAscii [eax],0
              invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL
            ...

ToAscii函数的用法是:

  invoke ToAscii,dwVirtKey,uScanCode,lpKeyState,lpBuffer,uFlags

dwVirtKey参数指定按键的虚拟码,在使用时直接用钩子回调函数的wParam参数就可以了,uScanCode指定按键的扫描码,并用位15来表示是按键按下还是按键释放,和回调函数的lParam参数对比可以看出,lParam参数的高16位就是需要的东西,所以程序将lParam右移16位后用做uScanCode参数。

lpKeyState指向一个256字节的缓冲区,其中存放键盘中所有按键的当前状态,一个字节表示一个按键,数值为1表示按下,为0表示释放,数据在缓冲区中的排列位置按照VK_xx虚拟码的顺序排列。这是为了让函数得知键盘上各种控制键的状态(如Shift,Alt和Ctrl等),因为这些键是否按下对转换结果是有影响的,比如同样是按键“1”,如果Shift键不按下,对应的就是“1”,按下的话函数必须返回“!”才是正确的结果。当然不可能自己去填写这个缓存区,使用GetKeyboardState函数就可以让系统根据当前的键盘状态填写这个缓冲区。

lpBuffer指向一个缓存区,用来接收转换后的ASCII码,最后的uFlags参数表示当前是否有一个菜单在激活状态,0表示没有,1表示有菜单正在激活。

函数的返回值表示转换后返回在lpBuffer缓冲区中的字符数量,它可能是0(如按键放开时不产生字符)、1或者是2,下面的语句根据返回字符数将缓冲区中的字符尾部加上一个NULL:

  mov    byte ptr szAscii [eax],0

对于Shift等控制键来说,GetKeyboardState函数返回的状态是区分左、右键的(分别对应VK_LSHIFT和VK_RSHIFT),而ToAscii函数检测的是VK_SHIFT,不对Shift键进行处理的话,转换结果可能是错误的,所以程序使用GetKeyState函数单独获取VK_SHIFT的状态并手工修改缓冲区中VK_SHIFT位置的状态。

转换完成后,用PostMessage函数将转换后的按键内容传递给主窗口,就大功告成了!不过要注意的还有两点:首先是在这里不要使用SendMessage函数,因为可能造成死循环;其次就是不要向主窗口传递地址,因为钩子DLL被插入到其他进程的地址空间中运行,所以将地址传回去可能是无效的。

不同类型钩子回调函数返回值的定义是不同的。对于键盘钩子,返回0表示允许Windows将消息转发给目标窗口过程,返回非0值表示让Windows将消息丢弃,这样钩子函数可以检测到按键动作,目标程序却无法收到键盘消息,相当于所有的按键都失效了。

3. 钩子链

Windows系统中可以同时存在多个同类型的钩子,多个程序同时安装同一种钩子的时候就会出现这种情况,这些钩子组成一个钩子链,最近加入的钩子放在链表的头部,Windows负责为每种钩子维护一个钩子链。当一个事件发生的时候,Windows调用最后安装的钩子,然后由当前钩子的回调函数发起调用下一个钩子的动作,Windows收到这个动作后,再从链表中取出下一个钩子的地址并将调用传递下去。

在大多数的情况下,一个钩子回调函数最好把消息事件传递下去以便其他的钩子都有获得处理这一消息的机会。调用下一个钩子函数是CallNextHookEx,该函数的用法是:

  invoke CallNextHookEx,hHook,dwCode,wParam,lParam

hHook参数是当前钩子的句柄,dwCode,wParam和lParam参数就是当前钩子收到的参数,这个函数让Windows调用钩子链中的下一个钩子。如果调用成功,函数的返回值是下一个钩子回调函数返回的数值。

11.2.3 日志记录钩子

日志记录钩子是一种特殊的钩子,说它特殊是因为它是远程钩子,却不用放在动态链接库中,这就为监视系统范围的消息提供了方便。本节中尝试用日志记录钩子的办法来实现键盘监视的功能,源代码包括在所附光盘的Chapter11\RecHook目录中,包括汇编源文件RecHook.asm和资源脚本文件RecHook.rc,其中RecHook.rc文件的内容和上一个例子的Main.rc文件是一样的。

RecHook.asm文件的内容如下:

                 .386
                 .model flat, stdcall
                 option casemap :none
;####################################################################
; Include 文件定义
;####################################################################
include        windows.inc
include        user32.inc
includelib     user32.lib
include        kernel32.inc
includelib     kernel32.lib
;####################################################################
; Equ 等值定义
;####################################################################
ICO_MAIN          equ    1000
DLG_MAIN          equ    1000
IDC_TEXT         equ    1001

上页:第11章 动态链接库和钩子 · 11.2 Windows钩子(3) 下页:第11章 动态链接库和钩子 · 11.2 Windows钩子(5)

第11章 动态链接库和钩子

版权所有 © 云南伯恩科技 证书:粤ICP备09170368号