📄 20.1.2 进程内钩子.txt
字号:
20.1.2 进程内钩子
首先编程实现一个进程内的钩子。新建一个基于对话框的 MFC工程,工程取名为: InnerHook,并删除自动创建的对话框资源中默认存在的静态文本控件。
1.安装鼠标钩子
如果想监视鼠标消息,首先就需要定义相应的鼠标钩子过程,该钩子过程的定义形式如下所示:
LRESULT CALLBACK MouseProc(int nCode , WPARAM wParam, LPARAM lParam);
该函数具有三个参数,各参数的含义及取值如下所述。
. nCode
确定钩子过程如何处理当前消息,此参数可以是表 20.2中所列值之一。
表 20.2 nCode参数的取值
nCode取值 说明
HC_ACTION 表明参数 wParam和 lParam包含了关于鼠标消息的信息
HC NOREMOVE 表明参数 wParam和lParam包含了关于鼠标消息的信息,而且此鼠标消息尚未从消息队列中删除 (应用程序调用 Pee kMessage函数并设置了 PM_NOREMOVE标志)
. wParam
指示鼠标消息的标识。
. lParam
指向 MOUSEHOOKSTRUCT结构体指针。这个参数并不重要,因此不详细讲述,如果读者感兴趣的话,可自行查阅相关资料。
在钩子过程中对信息处理完成之后,如果想把信息继续传递给下一个钩子过程,可以调用 CallNextHookEx函数来实现。该函数的功能是把钩子信息传递给钩子链中下一个等待接收信息的钩子过程。该函数的声明如下所示:
LRESULT CallNextHookEx (HHOOK hhk, int nCode , WPARAM wParam, LPARAM lParam) ;
其中第一个参数 (hhk.)指定当前钩子过程句柄,就是调用 SetWindowsHookEx函数后得到的返回值,其他三个参数与 MouseProc函数相应参数相同。
如果钩子函数返回非 0值,表示已经对当前消息进行了处理。这样,系统就不会再将这个消息传递给目标窗口过程了。因此,如果钩子过程对当前消息进行了处理,则应返回一个非 O值,以避免系统再次将此消息传递给目标窗口过程:否则建议调用 CallNextHookEx函数井返回该函数的返回值,以便其他安装了 WH_MOUSE类型钩子过程的应用程序获得相应的钩子通知。在本例中,我们可以在 CInnerHookDlg类的 OnInitDialog函数定义之前定义鼠标钩子过程,并让它直接返回非 O值,这样系统将不再继续传递鼠标消息,相当于把鼠标消息屏蔽掉了。本例实现的鼠标钩子过程具体代码如例 20-1所示。
例J 20-1
LRESULT CALLBACK MouseProc( int nCode , WPARAM wParam, LPARAM lParam)
return 1 ;
为了保存 SetWindowsHookEx函数返回的鼠标钩子过程句柄,应该再定义一个 HHOOK类型的全局变量并将其初始化为 NULL,其定义位置可放置在上述例 20-1所示 MouseProc函数的前面:
HHOOK g_hMouse = NULL; .
然后,在 ClnnerHookDlg类的 OnlnitDialog函数中调用 SetWindowsHookEx函数,创建鼠标钩子过程。即添加例 20-2所示代码中加灰显示的那行代码:例 20-2 .
BOOL CInnerHookDlg : :OnInitDialog()
CDialog : :OnlnitDialog();
…...
// TODO: Add extra initiali zation here
g_hMouse =SetWindowsHookEx (WH_MOUSE , MouseProc ,NULL, GetCurrentThreadId () ) ;
return TRUE ; // return TRUE unless you set the focus to a control
因为这里创建的是鼠标钩子过程,所以上述如例 20-2所示代码调用 SetWindows HookEx函数时,将其第一个参数指定为 WH_MOUSE;第二个参数就是上面定义的鼠标钩子过程名称:对于第三个参数因为这里创建的钩子过程是和当前进程中的当前线程,即主线程相关的,所以,将此参数设置为 NULL;对于第四个参数,既然钩子过程与当前线程相关,这里就应该传递当前线程的 ID,该 E可以通过调用 GetCurren{τ'hreadld函数获得。
Build并运行 InnerHook程序,然后在程序窗口中任意单击鼠标按键,将会发现鼠标按键己经不起作用了。我们可以按下回车键或空格键来关闭这个程序窗口,因为当前焦点位于默认按钮:"确定"上,所以当按下回车键或空格键时,程序将执行 OnOK函数,后者将关闭对话框窗口。
2.安装键盘钩子
为了给程序安装键盘钩子,首先就需要定义键盘钩子过程。
(1)屏蔽所有键盘按钮消息
如果希望屏蔽所有的键盘按键消息,可以直接让该过程返回非 0值,表示己经处理了键盘消息。因此,键盘钩子过程的具体代码如例 20-3所示。
例 20-3
LRESULT CALLBACK KeyboardProc(int code , WPARAM wParam, LPARAM lParam )
return 1 ;
注意:键盘钩子过程的声明形式可参考 MSDN中提供的帮助。
同样,为了保存 SetWindowsHookEx函数返回的键盘钩子过程句柄,应该再定义一个 HHOOK类型的全局变量并初始化为 NULL:
HHOOK g_hKeyboard=NULL;
.
然后在 CInnerHookDlg类的 OnInitDialog函数中调用 SetWindowsHookEx函数,创建键盘钩子过程。即添加如例 20-4所示代码中加灰显示的那行代码。
.
例 20-4
BOOL ClnnerHookDlg: :OnlnitDialog()
CDia1og : : On工 nitDialog() ;
…...
// TODO: Add extra initialization here
g_hMouse = SetWindowsHookEx(WH_MOUSE, MouseProc , NULL , GetCurrentThreadld ( ) ) ;
g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD ,KeyboardProc , NULL , GetCurrentThreadld()) ;
return TRUE ; // return TRUE unless you set the focus to a control
Build并运行InnerHook程序,可以发现此时程序不仅对鼠标按钮没有反应了,而且当按下回车键或空格键时,也不起作用了。也就是说,现在程序对鼠标和键盘消息都不响应了,为了关闭程序,只能通过任务管理器来强制终止该程序。
(2)仅屏蔽空格键消息
也可以在键盘消息处理中只屏蔽某些特定的按键,例如屏蔽空格按键,这样就需要在钩子过程中对到来的消息进行判断,找到需要处理的消息井作相应处理。读者可以看到,上述如例20-3所示代码定义的键盘钩子过程(KeyboardProc函数)有三个参数,其中参数 code与鼠标钩子过程的第一个参数含义相同:参数wParam是产生当前按键消息的键盘按
的虚拟键代码,这是Windows定义的与设备无关的,键盘按键的代码。的按键时,它实际上发送的是一个脉冲信号, Windows定义了一些虚拟键代码来表示这些信号,并由键盘设备驱动程序负责解释。因此,在钩子过程中就可以通过wParam参数得到当前按下的是哪个按键,例如,如果想要判断当前按下的是否是空格按键,就可以判断 wParam参数的值是否是YK_SPACE宏(空格键的虚拟键码)的值,如果是,那么钩子过程就返回1.表示己经对空格键进行了处理;否则,将按键消息传递给下一个钩子过程,如果没有下一个钩子过程了,消息将最终传递到程序窗口。
键盘虚拟键的宏都是以 "VK..开头的。
为了将消息传递给下一个钩子过程,需要调用 CallNextHookEx函数,该函数的第一个参数是当前钩子过程的句柄,其余三个参数与键盘钩子过程KeyboardProc的三个参数一样,因此可以直接把KeyboardProc的三个参数值传递给CallNextHookEx函数的相应参数。修改后的仅屏蔽空格键的键盘钩子过程代码如例20-5所示。
例20-5
LRESULT CALLBACK KeyboardProc( int code, WPARAM wPararn , LPARAM lParam)
{
if(VK_SPACE==wParam)
return 1;
else
return CallNextHookEx(g_hKeyboard, code ,wParam, lParam);
再次运行InnerHook程序,此时按下空格键时,程序没有反映,但是按下回车键后,程序将退出。说明键盘钩子过程现在只是屏蔽了空格键。
如果也想屏蔽回车键,只需要在上述键盘钩子过程中再多加一个判断条件:如果 wParam是回车键虚拟键 (YKRETURN),则也直接返回值: 1。这时,键盘钩子过程代码如例下所示。
LRESULT CALLBACK KeyboardProc( int code , WPARAM wparam , LPARAM lParam )
{
if(VK_SPACE==wParam || VK_RETURN==wParam)
return 1 ;
else
return CallNextHookEx(g_hKeyboard,code,wParam,lParam) ;
注意:并不需要记住所有按键的虚拟键,只需要记住常用的虚拟键代码的
宏,然后在VC++开发环境中,在常用虚和L键宏(例如VK_SPACE)上学击鼠标右键,并从弹出的快捷菜单中选择[Go To Defmition Of VK_SPACE ],就会定位到该宏的定义代码处,从而就可以看到其他接键的虚拟键宏的定义(位于 WinUser.h文件中,代码如例20-7所示)。至于VK_O到VK_9,实际上与ASCII码的0到9是一样的, VK_A到VK_Z与ASCII码的A到Z是一样的,因此就没有定义这些VK_宏。
例20-7
#define VK_SPACE Ox20
#define VK PRIOR Ox21
#define VK_NEXT Ox22
#define VK_END Ox23
#define VK_HOME Ox24
#define VK_LEFT Ox25
#define VK_UP Ox26
#define VK_RIGHT Ox27
#define VK_DOWN Ox28
#define VK_SELECT Ox29
#define VK_PRINT Ox2A
#define VK_EXECUTE Ox2B
#define VK_SNAPSHOT Ox2C
#define VK_NSERT Ox2D
#define VK_DELETE Ox2E
#define VK_HELP Ox2F
1* VK_0 thru VK_9 are the same as ASCii , 0' thru '9' ( Ox30 -Ox39) 食/ 1* VK_A thru VK_Z are the same as ASC工1 'A' thru 'Z' (Ox41 -Ox5A) *1
然后再次运行InnerHook程序,可以发现这时按下键盘上的空格键或回车键,程序都没有反应,说明程序己经将这两个按键消息都屏蔽了。这时可以按下键盘上的 Alt+F4键来关闭本程序。
(3)屏蔽Alt+F4组合键消息
如果我们也想屏蔽键盘上的Alt+F4键消息,那么应该如何处理呢?因为这时是两个按键,所以与上述的处理稍有些不同,具体的实现代码如例20-8所示。
例 20-8
LRESULT CALLBACK KeyboardProc(int code , WPARAM wparam, LPARAM lParam)
{
//if(VK_SPACE==wParam || VK_RETURN==wParam)
if(VK_F4==wParam && (1==(lParam>>29 & 1)))
return 1;
else ,
return CallNextHookEx(g_hKeyboard,code,wParam,lParam);
读者应该已经看到了,在键盘钩子过程函数中,还有一个参数: lParam,它是一个 32位的整数,它的每一位或某些位表示特定的含义,用来指定按键重复的次数、扫描码、扩展键标记、上下文代码等标记。此参数可以是表 20.3所列各值的组合值。
表 20.3 lParam参数各位的含义
位 含义
。-15位 指不重复次数,此值记录了由于用户连续按键引发的按键重复次数
16-23位 指不扫描码,此值依赖于键盘生产厂家
第 24位 指示当前按键是否是功能键或数字小键盘上的键,如果是,其值为 1
25-28位 保留未用
第 29位 上下文代码,如果 Alt键被按下,则其值为 1,否则为 0
第 30位 指不当前的键状态,如果在此消息被发送之前该键是按下的,其值为 1,否则为 0
第 31位 指示变化状态,如果此键正在被按下,则其值为 0
根据表 20.3中所列的内容,我们可以知道键盘钩子过程的 lParam参数的第 29位表示的是上下文代码,如果该位的值是 1,就表示按下了Alt键。这样的话,我们就可以根据这一位来判断用户是否按下了Alt键。为了判断 29位的值,可以使用 C语言中的移位操作,将 lParam右移 29位后(右移的是 0~28位),它的第 29位就变成了最右边的位,然后再和数值 1进行与操作,对数值 1来说,只有最右边的一位是 1,其他位都是 0,经过这样的与操作后,就能保证得到的结果除了最右边的一位以外,其他所有位都是 0。所以如果这时 lParam最右边一位(即原来的第 29位)是1,则经过与操作后得到的数为 1;如果最右边一位是 0,则经过与操作后得到的数为 0。本例中,除了需要判断A1t键的按下状态以外,同时还需要判断 wPar阻1是否是 F4键。
再次运行InnerHook程序,按下 Alt+F4键,程序没有反应,说明程序已经屏蔽了这一组合按键消息,此时可以按空格或回车键退出本程序。
(4)只能按某个特定键退出程序在实际编写程序过程中,可能还有这样的情况:希望程序一方面能屏蔽鼠标和键盘消息,但又希望该程序能留一个"后门",例如,按下F2键退出程序。
这时在键盘钩子过程中,就需要进行判断:如果 wParam等于 VK_F2,就让程序退出:在其他情况下,都是将键盘消息屏蔽掉。为了让程序退出,可以给应用程序的主窗口发送一条 WM_CLOSE消息,让应用程序从主窗口退出,从而程序也就退出了。
可以调用 SendMessage函数来发送消息,但这里的键盘钩子过程是一个全局函数,因
此在它内部只能调用全局的SendMessage函数。该函数的第一个参数是目标窗口的句柄,对本例来说, ClnnerHookDlg对象的成员变量m hWnd保存了这个窗口句柄。这里可以在 InnerHookDlg叩p文件中再定义一个全局窗口句柄变量,井将其初始化NULL:
HWND g_hWnd=NULL;
然后在ClnnerHookDlg类的OnInitDialog函数中,将这个新定义的全局变量赋值为该对话框类的成员变量m hWnd,具体代码如例20-9所示代码中加灰显示的那条代码。
例20-9
BOOL CInnerHookDlg : : On工nitDialog ( )
CDialog : : On工nitDialog() ;
// TODO: Add extra initializat工on here
g_hWnd = m_hWnd;
g_hMouse =SetWindowsHookEx(WH_MOUSE ,MouseProc ,NULL ,GetCurrentThreadld());
g_hKeyboard=SetWindowsHookEx (WH_KEYBOARD ,KeyboardProc ,NULL ,GetCurrentThreadId ( ) ) ;
return TRUE ; // return TRUE unless you set the focus to a control
这样,在键盘钩子过程中就可以调用全局 SendMessage函数向程序主窗口发送 WM CLOSE消息了。在发送关闭消息之后,最好将己安装的钩子过程移除,这可以通过调用UnhookWindowsHookEx函数来实现,该函数的功能是从钩子链中移走一个己安装的钩子。该函数的声明如下所示=
BOOL UnhookWindowsHookEx( HHOOK hhk) ;
可以看到UnhookWindowsHookEx有一个参数,就是希望移除的钩子过程的句柄,也就是先前调用SetWindowsHookEx函数得到的返回值。因此,这时的键盘钩子过程的具体代码如例20-10所示。
例20-10
LRESULT CALLBACK KeyboardProc(int code , WPARAM wParam, LPARAM lParam)
// if(VK_SPACE==wParam VK_RETURN二=wParam)
/* if(VK_ F4==wParam && (1==(lParam>>29 & 1)))
return 1 ;
else
return CallNextHookEx(g_hKeyboard,code,wParam,lParam);*/
if(VK_F2==wParam)
{
: : SendMessage (g_hWnd,WM_CLOSE , 0,0) ;
UnhookWindowsHookEx(g_hKeyboard) ;
UnhookWindowsHookEx(g_hMouse) ;
return 1;
Build井运行InnerHook程序,将会发现程序对鼠标和其他键盘按键都没有响应,当按下F2键时,程序退出。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -