当前位置:文档之家› 第4章钩子函数和窗口子类化

第4章钩子函数和窗口子类化

第4章钩子函数和窗口子类化钩子是操作系统消息处理的一种机制。

通过钩子,应用程序可以安装一个钩子回调过程让系统调用,从而监视系统中的消息队列,在这些消息到达目标窗口之前对这些消息进行处理。

本章主要介绍钩子函数的基本概念以及几种常用钩子的应用举例。

4.1 钩子函数早在Windows3.x的时候,就有了钩子函数,它经历了Windows9x/NT/2000/XP/2003各个操作系统,始终保持了最大的兼容性。

可以说大部分的钩子函数适用于现在所有的Win32操作系统,钩子函数在系统编程方面有着广泛的应用前景。

首先应该承认钩子会降低系统的性能,因为它增加系统处理每一个消息的开销,所以用户除非必要才安装钩子,而且还要尽可能早地去除钩子。

操作系统支持多种类型的钩子,每种类型都提供了它特有的消息处理机制,比如应用程序使用WH_MOUSE钩子只能监视鼠标的消息队列。

对于每种类型的钩子,系统都维护一个各自独立的钩子链,钩子链是一个指向用户提供的回调函数钩子过程的链表指针。

当与特定类型的钩子相关的窗口消息发生时,系统会把消息依次传递给钩子链中的每一个回调过程,传递的过程由用户定义的回调过程实现。

一般情况下,用户提供的钩子回调过程必须调用钩子链中的下一个回调过程。

否则钩子处理可能会中断,出现不可预测的结果。

钩子过程可以监视窗口消息,也可以修改甚至停止钩子消息的继续传递,不让它到达钩子链中的下一个目标过程。

钩子过程需要用户调用SetWindowsHookEx函数进行安装。

钩子过程一般遵循下面的调用规范。

LRESULT CALLBACK HookProc(intnCode,WPARAMwParam,LPARAMlParam);其中HookProc是应用程序提供的函数名。

nCode参数是一个钩子标识码,钩子过程会利用它决定下一步进行的操作。

这个标识码的值与安装的钩子类型有关。

每种类型都有它的自身定义。

后面两个参数的定义依赖于nCode参数,一般用于存放与窗口消息相关的内容。

SetWindowsHookEx函数会自动安装一个钩子过程,这个过程位于钩子链表的头部,最后安装的钩子函数总是最先得到响应。

前面的钩子处理过程可以决定是否调用钩子链中的下一个过程,这可以通过调用CallNextHookEx函数实现。

注意:某些钩子类型能够监视发生的窗口消息系统自动把消息依次传递给钩子链中的每一个钩子过程,而不管用户是否调用CallNextHookEx函数。

全局钩子会监视同一桌面环境下所有的窗口消息,而线程钩子只能监视单个线程内发生的消息。

由于全局钩子能够在同一桌面的所有应用环境下调用,所有这个钩子过程必须在一个动态链接库中实现。

注意:全局钩子一般只用于调试目的,应尽可能地避免使用。

全局钩子会显著地降低系统的性能,增加系统的开销,并可能会与安装同一全局钩子的应用程序发生冲突。

钩子函数的处理应该尽可能简单,并要快速退出。

对于处理复杂的过程,可以借助于发送异步处理窗口消息的方式实现。

操作系统提供了以下一些钩子,这些钩子允许用户监视系统消息处理的某一个方面。

如表4-1所示:安装钩子函数要用到SetWindowsHookEx函数。

对于全局钩子而言,钩子过程必须在一个动态链接库模块中实现,这个过程必须作为动态链接库的输出函数,以便能够在安装钩子程序中通过调用LoadLibrary/GetProcAddress函数获得回调过程的地址,然后把回调函数的地址传递给SetWindowsHookEx函数。

HOOK PROC hkprcSys Msg;Static HINSTANCE hinstDLL;Static HHOOK hhookSysMsg;hinstDLL=LoadLibrary((LPCTSTR)"c:\\windows\\sysmsg.dll");hkprcSysMsg=(HOOKPROC)GetProcAddress(hinstDLL,"SysMessageProc");hhookSysMsg=SetWindowsHookEx(WH_SYSMSGFILTER,hkprcSysMsg,hinstDLL,0);同样释放全局钩子函数要用到UnhookWindowsHookEx,这个钩子函数本身不会释放包含钩子过程的动态链接库。

这是因为全局钩子会被同一桌面的所有应用程序调用,系统会自动调用LoadLibrary函数,把实现钩子过程的动态链接库映射到受它影响的当前进程的地址空间中去。

同样,最后系统会自动在所有使用该动态链接库的应用程序不再使用这个钩子时,调用FreeLibrary函数释放动态链接库。

编写全局钩子,最好不要使用MFC,也尽可能地不要使用C运行库函数,应该尽可能地使用API函数代替使用这些函数,比如常用lstrcpy代理strcpy、_tcscpy等。

这是因为全局钩子会被映射到受它影响的所有进程的地址空间,一个不依赖这些运行库的运行的程序可能会产生更多的潜在冲突。

比如一个采用VisualBasic或者Java编写的应用程序,它们不会使用MFC类库。

另外全局钩子中的全局变量,当映射到某个进程中时,它将变成属于该进程所有的私有变量,不能被其他进程共享,因为它们映射的地址是不同的。

为了实现变量共享,可以使用前面谈到的共享节的办法。

#pragma data_seg(".Share")HANDLE hWnd=NULL;#pragma dta_seg()#pragma comment(linker,"/section:.Share,rws")当然还可以借助于其他方式进行通信,比如共享内存等。

如果用户采用窗口消息(SendMessge、PostMessage),一定不要采用传递指针的方法,因为在另外一个进程中无法访问另外一个进程的地址空间。

4.2 键盘钩子的应用键盘钩子有着广泛的用途,比如在WindowsNT环境下,用户可以直接调用GetLastInputInfo函数获得上一次输入的信息,通过这个信息得到一个当时的时间戳,通过该时间决定在若干时间内没有用户输入(键盘和鼠标)触发某些任务。

然而Windows9x并没有提供这样的函数,为了得到上一次键盘和鼠标动作的时间,就必须实现一个全局键盘和鼠标,记录钩子函数回调时发生的时间。

另外键盘钩子禁止用户在多个进程间切换。

Windows提供的多任务机制使用户可以自由地在多个应用程序间自由切换,每一个应用程序作为一个进程都拥有独立的进程地址空间,各个程序之间互不影响。

特别是在WindowsNT环境下,一个应用程序的挂起,一般不会影响其他程序的运行,操作系统可以很轻松地把挂起的进程杀死,从而使系统得到正常响应。

然而这种机制同时也会助长用户同时运行多个程序,开多个窗口,从而使系统不堪重负,反应迟缓。

这对于普通的个人用机不会有什么影响,但对于工业实时控制计算机而言,情况就大不一样。

由于WindowsNT 并不是一个实时的操作系统,一个程序的运行尽管不会直接影响其他程序,但是如果它对系统资源如CPU、内存占用过多,就会直接影响其他程序的快速响应,比如完全格式化一个质量不好的软盘、浏览次品光盘、刻录光盘、复制大文件等。

因此对于运行重要程序的计算机而言,重要程序应该独占系统资源,禁止任务切换,以便提高系统的实时性和可靠性,以防意外事件发生。

另外,对于文献检索的公共机房,机房工作人员一般也不希望检索人员来回切换程序。

(1)在Windows9x环境下,应用程序可以通过不同的参数调用SystemParametersInfo函数,实现允许和禁止任务切换。

方法如下:例4-1Windows9x环境下禁止任务切换。

UINT nPreviousState;SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,TRUE,&nPreviousState,0); //禁止任务切换SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,FALSE,&nPreviousState,0); //允许任务切换只是应用程序退出前,必须恢复允许任务切换状态。

(2)对于WindowsNT4.0ServicePack3或更高版本,包括Windows2000和WindowsXP,应用程序可以通过安装低级键盘钩子(WH_KEYBOARD_LL)实现禁止任务切换。

在Windows9x/Me环境下不起作用。

#define _WIN32_WINNT 0x0400HHOOK hhkLowLevelKybd;LRESULT CALLBACK LowLevelKeyboardProc(int nCode,WPARAM wParam,LPARAM lParam){ KBDLLHOOK STRUCT*pkbhs=(KBDLLHOOKSTRUCT*)lParam;BOOL bControlKeyDown=0;switch(nCode){ Case HC_ACTION:bControlKeyDown=GetAsyncKeyState(VK_CONTROL)>>((sizeof(SHORT)*8) -1); //检查是否按下Ctrl键if(pkbhs->vkCode==VK_ESCAPE&&bControlKeyDown) return 1; //禁止Ctrl+Escif(pkbhs->vkCode==VK_TAB&&pkbhs->flags&LLKHF_ALTDOWN) return 1; //禁止Alt+Tabif(pkbhs->vkCode==VK_ESCAPE&&pkbhs->flags&LLKHF_ALTDOWN) return 1; break; //禁止Alt+Escdefault: break;return CallNextHookEx(hhkLowLevelKybd,nCode,wParam,lParam); }int WINAPIWinMain(HINSTANCE hinstExe,HINSTANCE,PTSTR pszCmdLine,int){ hhkLowLevelKybd=SetWindowsHookEx(WH_KEYBOARD_LL,LowLevelKeyboardProc,hinstExe,0); //安装钩子过程MessageBox(NULL,TEXT("Alt+Esc,Ctrl+Esc,and Alt+Tab are now disabled.")TEXT("Click"Ok" to terminate this application and re-enable these keys."),TEXT("Disable Low-Level Keys"),MB_OK);UnhookWindowsHookEx(hhkLowLevelKybd); return(0); }(3)在WindowsNT环境下还有一种方法,通过枚举窗口,禁止除当前窗口以外的所有窗口,程序退出前,恢复这些窗口为允许状态。

相关主题