Windows钩子简介
站长推荐:NSetup一键部署软件
一键式完成美化安装包制作,自动增量升级,数据统计,数字签名。应对各种复杂场景,脚本模块化拆分,常规复杂的脚本代码,图形化设置。无需专业的研发经验,轻松完成项目部署。(www.nsetup.cn)
钩子(HOOK)是windows一种消息处理机制。一个钩子应用程序可以截获windows消息进行处理,并控制消息的流动。钩子起作用的原因是因为windows系统提供了钩子链。这个钩子链就是应用程序定义的钩子处理函数队列。当某种类型消息产生时,系统将消息传递到钩子链的第一个处理函数,该处理函数处理完,再决定是否将消息传递到链中的下一个处理函数。如果某个钩子处理函数获得消息后没有继续传送,则其以后的处理函数则无法获得应有的消息。(注意:对于某些类型的HOOK,不管HOOK链中的处理函数是否向下传递消息,与此类型HOOK关联的所有HOOK函数都会收到系统发送的消息。)最近安装的钩子总是放在钩子链的最前端,而最早安装的则放在最尾端。但是windows并不要求钩子的卸载顺序一定与安装顺序相反,只要有钩子卸载,系统便释放其占用的内存,并更新整个HOOK链表。如果程序安装了钩子,但还没使用程序便结束了,则系统会自动完成卸载操作。
钩子根据其对消息监视的范围的不同而分为系统钩子和线程钩子两大类: 线程钩子(局部钩子)只能监视本进程中某个指定线程的事件消息。一般在当前线程或当前线程派生的线程内。 系统钩子(全局钩子)是监视当前系统下运行的所以线程的事件消息。系统钩子可以监视系统中所有的应用程序,所以在使用系统钩子时必须放在独立的动态链接库(DLL)中。系统自动将这个DLL映射到受钩子函数影响的所有进程的地址空间中。 如果对于同一类型事件(如鼠标消息)既安装了线程钩子有安装了系统钩子,那么windows系统默认线程钩子优先级要高,即先调用线程钩子再调用系统钩子。对同一事件消息可安装多个钩子处理函数,这些钩子处理函数便形成了钩子链。(注意:系统钩子在使用过程中会延长消息处理时间,降低系统性能,所以在使用完毕要及时卸载。)
可以把钩子理解成一个回调函数,所以它的语法应符合回调函数的语法。其具体形式参考如下: LRESULT CALLBACK HookProc // HookProc是钩子函数名称,CALLBACK也可换成WINAPI { int nCode; // 确定钩子的行为,依赖于HOOOK的类型,每一种HOOK类型都有自己的HOOK代码特征字符集 WPARAM wParam; // 与回调函数相似,包含关于发送或接收消息的信息 LPARAM lParam; // 与回调函数相似,包含关于发送或接收消息的信息 }; windows针对不同的HOOK类型给出其钩子函数的形式,参数、返回值等都相同,只有钩子函数名和具体的参数取值不同。已有的HOOK函数有:CallWndProc、CallWndRetProc、CBTProc、DebugProc、ForegroundIdleProc、GetMsgProc、JournalPlaybackProc、JournalRecordProc、KeyboardProc、LowLevelKeyboardProc、LowLevelMouseProc、MessageProc、MouseProc、ShellProc、SysMsgProc。(具体的参数说明参考MSDN)
光定义了钩子处理函数还无法起作用,必须将钩子放入钩子链中它们才能发挥功能。将钩子放入钩子链需要SetWindowsHookEx函数的帮助。 HHOOK SetWindowsHookEx( int idHook, // 欲安装的钩子类型 HOOKPROC lpfn, // 钩子处理函数指针 HINSTANCE hMod, // 应用程序实例句柄 DWORD dwThreadId // 与钩子相关联的线程ID ); idHook指定了钩子处理函数的类型,它决定了钩子能处理什么事件消息。其取值主要有以下几种: 钩子类型 描述 WH_CALLWNDPROC 监视系统将要传送给目标窗口处理函数的消息的钩子 WH_CALLWNDPROCRET 监视已被目标窗口处理函数处理后的消息的钩子 WH_CBT 基于CBT(computer-based training)应用程序钩子 WH_DEBUG 用于其他钩子处理函数调试的钩子 WH_FOREGROUNDIDLE 当应用程序前台线程将要转向空闲时被调用的钩子 WH_GETMESSAGE 监视进入消息队列的消息钩子 WH_JOURNALPLAYBACK 回放记录输入消息钩子 WH_JOURNALRECORD 记录进入系统消息队列的输入消息钩子 WH_KEYBOARD 监视键盘消息钩子 WH_KEYBOARD_LL 监视低级键盘输入事件钩子 WH_MOUSE 监视鼠标消息钩子 WH_MOUSE_LL 监视低级鼠标输入事件钩子 WH_MSGFILTER 监视对话框、消息框、菜单或滚动条输入消息钩子 WH_SHELL 外壳钩子 WH_SYSMSGFILTER 监视系统消息钩子 lpfn指定钩子处理函数的地址指针。如果dwThreadId为0或是一个由其他进程创建的线程标识,则lpfn必须指向DLL中钩子处理函数。lpfn也可指向当前进程中的钩子处理函数。 hMod是包含钩子处理函数的DLL句柄。如果dwThreadId标识的线程由当前进程所创建,而且钩子处理函数也属于当前进程,则hMod必须置为NULL。 dwThreadId指定与钩子处理函数相关联的线程的标识符。如果该参数为0,则钩子处理函数与所有已存在的线程关联,即全局钩子。钩子处理函数与线程相关联是指在一个钩子链表中发给该线程消息的同时也发送给钩子,且该消息先被钩子处理。钩子本身占用一个线程。 函数如果执行成功返回钩子函数的句柄,如果失败则返回NULL。我们也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。但是这样的话,钩子链中其他的钩子就不会接到通知,这将有可能造成不可预料的结果。 SetWindowsHookEx函数总是改应用程序定义的钩子安装到钩子链表的开头。是否通过调用CallNextHookEx函数将消息传递给钩子链中下一个钩子函数是可选的,但是建议最好通过显式调用该函数将消息传递给钩子链中其他的钩子函数,除非你的目的就是阻止该消息的向下传递。
LRESULT CallNextHookEx( HHOOK hhk, // 当前钩子的句柄,由SetWindowsHookEx返回。 int nCode, // 传递给钩子处理函数的事件代码,同钩子处理函数中参数nCode意义相同 WPARAM wParam, // 传递给钩子处理函数的包含关于发送或接收消息的信息 LPARAM lParam // 传递给钩子处理函数的包含关于发送或接收消息的信息 ); 一个钩子处理完消息后,如果需要将消息传递给钩子链中的下一个钩子,就需要调用该函数。
BOOL UnhookWindowsHookEx( HHOOK hhk // 要卸载的钩子句柄 ); 钩子在使用完之后需要调用该函数进行卸载,否则会造成不可预知的结果。
下面是一个使用钩子监视键盘信息的例子: // 这两个文件,可以将其编译成动态链接库,再在自己的应用程序中调用接口函数Start和stop,至于是隐式调用还是动态加载,视你自己的应用程序而定,至于如何编译动态链接库DLL可参考我的另一篇文章(动态链接库创建于使用)。 头文件(hook.h): extern “C” __declspec(dllexport) BOOL WINAPI Start(); extern “C” __declspec(dllexport) void WINAPI Stop();
源文件(hook.cpp): #include <windows.h> #include “dll.h” #pragma data_seg(“Shared”) HHOOK mhook = NULL; HINSTANCE hInstance = NULL; #pragma data_seg() //#pragma comment(linker,”/section:Shared,rws”
LRESULT WINAPI KeyProc(int code, WPARAM wParam, LPARAM lParam) { if(code == HC_ACTION && (lParam & 0xc000ffff) == 1) { char *sName; BOOL b_Sft = ::GetAsyncKeyState(VK_SHIFT) >> ((sizeof(short) * 8)-1); if(b_Sft) { switch(wParam) { case ’1′:sName = “!”;break; case ’2′:sName = “@”;break; case ’3′:sName = “#”;break; case ’4′:sName = “$”;break; case ’5′:sName = “%”;break; case ’6′:sName = “^”;break; case ’7′:sName = “&”;break; case ’8′:sName = “*”;break; case ’9′:sName = “(“;break; case ’0′:sName = “)”;break; case ‘A’:sName = “A”;break; case ‘B’:sName = “B”;break; case ‘C’:sName = “C”;break; case ‘D’:sName = “D”;break; case ‘E’:sName = “E”;break; case ‘F’:sName = “F”;break; case ‘G’:sName = “G”;break; case ‘H’:sName = “H”;break; case ‘I’:sName = “I”;break; case ‘J’:sName = “J”;break; case ‘K’:sName = “K”;break; case ‘L’:sName = “L”;break; case ‘M’:sName = “M”;break; case ‘N’:sName = “N”;break; case ‘O’:sName = “O”;break; case ‘P’:sName = “P”;break; case ‘Q’:sName = “Q”;break; case ‘R’:sName = “R”;break; case ‘S’:sName = “S”;break; case ‘T’:sName = “T”;break; case ‘U’:sName = “U”;break; case ‘V’:sName = “V”;break; case ‘W’:sName = “W”;break; case ‘X’:sName = “X”;break; case ‘Y’:sName = “Y”;break; case ‘Z’:sName = “Z”;break; } } else { switch(wParam) { // 0~9 case ’1′:sName = “1″;break; case ’2′:sName = “2″;break; case ’3′:sName = “3″;break; case ’4′:sName = “4″;break; case ’5′:sName = “5″;break; case ’6′:sName = “6″;break; case ’7′:sName = “7″;break; case ’8′:sName = “8″;break; case ’9′:sName = “9″;break; case ’0′:sName = “0″;break;
// A~Z case ‘A’:sName = “a”;break; case ‘B’:sName = “b”;break; case ‘C’:sName = “c”;break; case ‘D’:sName = “d”;break; case ‘E’:sName = “e”;break; case ‘F’:sName = “f”;break; case ‘G’:sName = “g”;break; case ‘H’:sName = “h”;break; case ‘I’:sName = “i”;break; case ‘J’:sName = “j”;break; case ‘K’:sName = “k”;break; case ‘L’:sName = “l”;break; case ‘M’:sName = “m”;break; case ‘N’:sName = “n”;break; case ‘O’:sName = “o”;break; case ‘P’:sName = “p”;break; case ‘Q’:sName = “q”;break; case ‘R’:sName = “r”;break; case ‘S’:sName = “s”;break; case ‘T’:sName = “t”;break; case ‘U’:sName = “u”;break; case ‘V’:sName = “v”;break; case ‘W’:sName = “w”;break; case ‘X’:sName = “x”;break; case ‘Y’:sName = “y”;break; case ‘Z’:sName = “z”;break; } } //数字键
switch(wParam) { case VK_BACK:sName = “~”;break; case VK_NUMPAD1:sName = “1″;break; case VK_NUMPAD2:sName = “2″;break; case VK_NUMPAD3:sName = “3″;break; case VK_NUMPAD4:sName = “4″;break; case VK_NUMPAD5:sName = “5″;break; case VK_NUMPAD6:sName = “6″;break; case VK_NUMPAD7:sName = “7″;break; case VK_NUMPAD8:sName = “8″;break; case VK_NUMPAD9:sName = “9″;break; case VK_NUMPAD0:sName = “0″;break; case VK_MULTIPLY:sName = “*”;break; case VK_ADD: sName = “+”;break; case VK_SUBTRACT:sName = “-”;break; case VK_DECIMAL: sName = “.”;break; case VK_DIVIDE: sName = “/”;break; }
HWND H_wnd = ::GetForegroundWindow(); MessageBox(H_wnd,sName,”键盘”,MB_OK); }
return FALSE; }
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch(ul_reason_for_call) { case DLL_PROCESS_ATTACH: hInstance=(HINSTANCE)hModule; break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
extern “C” __declspec(dllexport) BOOL WINAPI Start() { if(mhook!=NULL) return FALSE; mhook=::SetWindowsHookEx(WH_KEYBOARD,KeyProc,hInstance,0); return mhook!=NULL; }
extern “C” __declspec(dllexport) void WINAPI Stop() { ::UnhookWindowsHookEx(mhook); }
学习日记,兼职软件设计,软件修改,毕业设计。
本文出自 学习日记,转载时请注明出处及相应链接。
本文永久链接: https://www.softwareace.cn/?p=355