WINDOWS键盘事件的挂钩监控原理及其应用技术
WINDOW的消息处理机制为了能在应用程序中监控系统的各种事件消息，提供了挂接 各种反调函数(HOOK)的功能。这种挂钩函数(HOOK)类似扩充中断驱动程序，挂钩上 可以挂接多个反调函数构成一个挂接函数链。系统产生的各种消息首先被送到各种 挂接函数，挂接函数根据各自的功能对消息进行监视、修改和控制等，然后交还控 制权或将消息传递给下一个挂接函数以致最终达到窗口函数。WINDOW系统的这种反 调函数挂接方法虽然会略加影响到系统的运行效率，但在很多场合下是非常有用 的，通过合理有效地利用键盘事件的挂钩函数监控机制可以达到预想不到的良好效 果。 一、在WINDOWS键盘事件上挂接监控函数的方法 WINDOW下可进行挂接的过滤函数包括11种： WH_CALLWNDPROC 窗口函数的过滤函数 WH_CBT 计算机培训过滤函数 WH_DEBUG 调试过滤函数 WH_GETMESSAGE 获取消息过滤函数 WH_HARDWARE 硬件消息过滤函数 WH_JOURNALPLAYBACK 消息重放过滤函数 WH_JOURNALRECORD 消息记录过滤函数 WH_MOUSE 鼠标过滤函数 WH_MSGFILTER 消息过滤函数 WH_SYSMSGFILTER 系统消息过滤函数 WH_KEYBOARD 键盘过滤函数 其中键盘过滤函数是最常用最有用的过滤函数类型，不管是哪一种类型的过滤函 数，其挂接的基本方法都是相同的。 WINDOW调用挂接的反调函数时总是先调用挂接链首的那个函数，因此必须将键盘挂 钩函数利用函数SetWindowsHookEx()将其挂接在函数链首。至于消息是否传递给函 数链的下一个函数是由每个具体函数功能确定的，如果消息需要传统给下一个函 数，可调用API函数的CallNextHookEx()来实现，如果不传递直接返回即可。 挂接函数可以是用来监控所有线程消息的全局性函数，也可以是单独监控某一线程 的局部性函数。如果挂接函数是局部函数，可以将它放到一个.DLL动态链接库中， 也可以放在一个局部模块中；如果挂接函数是全局的，那么必须将其放在一个.DLL 动态链接库中。挂接函数必须严格按照下述格式进行声明，以键盘挂钩函数为例： int FAR PASCAL KeyboardProc( int nCode,WORD wParam,DWORD lParam) 其中KeyboardProc为定义挂接函数名，该函数必须在模块定义文件中利用EXPORTS命 令进行说明；nCode决定挂接函数是否对当前消息进行处理；wParam和lParam为具体 的消息内容。 二、键盘事件挂接函数的安装与下载 在程序中可以利用函数SetWindowsHookEx()来挂接过滤函数，在挂接函数时必须指 出该挂接函数的类型、函数的入口地址以及函数是全局性的还是局部性的，挂接函 数的具体调用格式如下： SetWindowsHookEx(iType,iProc,hInst,iCode) 其中iType为挂接函数类型，键盘类型为WH_KEYBOARD,iProc为挂接函数地址，hInst 为挂接函数链接库实例句柄，iCode为监控代码－0表示全局性函数。 如果挂接函数需要将消息传递给下一个过滤函数，则在该挂接函数返回前还需要调 用一次CallNextHookEx()函数，当需要下载挂接函数时，只要调用一次 UnhookWindowsHookEx(iProc)函数即可实现。 如果函数是全局性的，那么它必须放在一个.DLL动态链接库中，这时该函数调用方 法可以和其它普通.DLL函数一样有三种： 1.在DEF定义文件中直接用函数名或序号说明： EXPORTS WEP @1 RESIDENTNAME InitHooksDll @2 InstallFilter @3 KeyboardProc @4 用序号说明格式为：链接库名.函数名(如本例中说明方法为KEYDLL.KeyboardProc)。 2.在应用程序中利用函数直接调用： 首先在应用程序中利用LoadLibrary(LPSTR "链接库名")将动态链接库装入，并取得 装载库模块句柄hInst,然后直接利用GetProcAddress(HINSTANCE hInst,LPSTR "函 数过程名")获取函数地址，然后直接调用该地址即可，程序结束前利用函数 FreeLibrary( )释放装入的动态链接库即可。 3.利用输入库.LIB方法 利用IMPLIB.EXE程序在建立动态链接库的同时建立相应的输入库.LIB，然后直接在 项目文件中增加该输入库。 三、WINDOWS挂钩监控函数的实现步骤 WINDOWS挂钩函数只有放在动态链接库DLL中才能实现所有事件的监控功能。在.DLL 中形成挂钩监控函数基本方法及其基本结构如下： １、首先声明DLL中的变量和过程； ２、然后编制DLL主模块LibMain()，建立模块实例； ３、建立系统退出DLL机制WEP()函数； ４、完成DLL初始化函数InitHooksDll(),传递主窗口程序句柄； ５、编制挂钩安装和下载函数InstallFilter()； ６、编制挂钩函数KeyboardProc()，在其中设置监控功能，并确定继续调下一个钩 子函数还是直接返回WINDOWS应用程序。 ７、在WINDOWS主程序中需要初始化DLL并安装相应挂钩函数，由挂接的钩子函数负 责与主程序通信； ８、在不需要监控时由下载功能卸掉挂接函数。 四、WINDOWS下键盘挂钩监控函数的应用技术 目前标准的104 键盘上都有两个特殊的按键，其上分别用WINDOW程序徽标和鼠标下 拉列表标识，本文暂且分别称为Micro左键和Micro右键，前者用来模拟鼠标左键激 活开始菜单，后者用来模拟鼠标右键激活属性菜单。这两个特殊按键只有在按下后 立即抬起即完成 CLICK过程才能实现其功能，并且没有和其它按键进行组合使用。 由于WINDOWS 系统中将按键划分得更加详细，使应用程序中很难灵活定义自己的专 用快捷键，比如在开发.IME等应用程序时很难找到不与WORD8.0等其它应用程序冲突 的功能按键。如果将标准104键盘中的这两个特殊按键作为模拟CTRL和ALT 等专用按 键，使其和其它按键组合，就可以在自己的应用程序中自由地设置专用功能键，为 应用程序实现各种功能快捷键提供灵活性。正常情况下WINDOWS 键盘事件驱动程序 并不将这两个按键的消息进行正常解释，这就必须利用键盘事件的挂钩监控函数来 实现其特定的功能。其方法如下： １、首先编制如下一个简单动态链接库程序，并编译成DLL文件。 #include "windows.h" int FAR PASCAL LibMain(HANDLE hModule,UINT wDataSeg, UINT cbHeapSize,LPSTR lpszCmdLine); int WINAPI WEP(int bSystemExit); int WINAPI InitHooksDll(HWND hwndMainWindow); int WINAPI InstallFilter(BOOL nCode); LRESULT CALLBACK KeyHook(int nCode,WORD wParam,DWORD lParam); static HANDLE hInstance; // 全局句柄 static HWND hWndMain; // 主窗口句柄 static int InitCalled=0; // 初始化标志 static HHOOK hKeyHook; FARPROC lpfnKeyHook=(FARPROC)KeyHook; BOOL HookStates=FALSE; int FAR PASCAL LibMain( HANDLE hModule, UINT wDataSeg, UINT cbHeapSize, LPSTR lpszCmdLine) { if (cbHeapSize!=0) UnlockData(0); hInstance = hModule; return 1; } int WINAPI WEP (int bSystemExit) { return 1;} int WINAPI InitHooksDll(HWND hwndMainWindow) { hWndMain = hwndMainWindow; InitCalled = 1; return (0); } int WINAPI InstallFilter(BOOL nCode) { if (InitCalled==0) return (-1); if (nCode==TRUE) { hKeyHook=SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)lpfnKeyHook,hInstance,0); HookStates=TRUE; } else { UnhookWindowsHookEx(hKeyHook); HookStates=FALSE; } return(0); } LRESULT CALLBACK KeyHook(int nCode,WORD wParam,DWORD lParam) { static BOOL msflag=FALSE; if(nCode>=0) { if(HookStates==TRUE){ if((wParam==0xff)|| //WIN3.X下按键值 (wParam==0x5b)||(wParam==0x5c)){//WIN95下按键值 if((i==0x15b)||(i==0x15c)){ //按键按下处理 msflag=TRUE; PostMessage(hWndMain,0x7fff,0x1,0x3L); } else if((i==0xc15b)||(i==0xc15c)){//按键抬起处理 msflag=FALSE; PostMessage(hWndMain,0x7fff,0x2,0x3L); } } } } return((int)CallNextHookEx(hKeyHook,nCode,wParam,lParam)); } 该程序的主要功能是监控键盘按键消息，将两个特殊按键Micro按下和抬起消息转换 成自定义类型的消息，并将自定义消息发送给应用程序主窗口函数。 ２、在应用程序主函数中建立窗口后，调用InitHooksDll()函数来初始化动态链接 库，并将应用程序主窗口句柄传递给链接库，然后调用InstallFilter()函数挂接键 盘事件监控回调函数。 InitHooksDll(hIMEWnd); //初始化DLL InstallFilter(TRUE); //安装键盘回调函数 ３、在应用程序主窗口函数处理自定义消息时，保存Micro按键的状态，供组合按键 处理时判断使用。 switch (iMessage) { case 0x7fff: //自定义消息类型 if(lParam==0x3L){//设置Micro键的状态 if(wParam==0x1) MicroFlag=TRUE; else if(wParam==0x2) MicroFlag=FALSE; } break; ４、在进行按键组合处理时，首先判断Micro键是否按下，然后再进行其它按键的判 断处理。 case WM_KEYDOWN: // 按键按下处理 if(MicroFlag==TRUE){//Micro键按下 if((BYTE)HIBYTE(wParam)==0x5b){ //Micro+"["组合键 ......//按键功能处理 } else if((BYTE)HIBYTE(wParam)==0x5d){ //Micro+"]"组合键 ......//按键功能处理 } } break; ５、当应用程序退出时应注意下载键盘监控函数，即调用InstallFilter(FALSE)函 数一次。 ６、利用本文提供的方法设置自己的应用程序功能按键，在保证程序功能按键不会 与其它系统发生冲突的同时，有效地利用了系统中现有资源，而且在实现应用程序 功能的同时灵活应用了系统中提供的各种功能调用。