1.Hooks
hook指出了系统消息处理机制。利用hook,可以在应用程序中安装子程序监视系统和进程之间的消息传递,这个监视过程是在消息到达目的窗口过程之前。 下面简述hook,并且解释在Win32系统下,如何使用hook编程。
2.About Hooks hook将使程序效率降低,因为它们增加了系统必须处理的消息总数。你应该在需要时才使用,并及时删除它。我将以下面的主题描述hook。
Hook Chains(hook链) 系统支持很多不同类型的hooks;不同的hook提供不同的消息处理机制。比如,应用程序可以使用WH_MOUSE_hook来监视鼠标消息的传递。 系统为不同类型的hook提供单独的hook链。hook链是一个指针列表,这个列表的指针指向指定的,应用程序定义的,被hook过程调用的回调函数。当与指定的hook类型关联的消息发生时,系统就把这个消息传递到hook过程。一些hook过程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个hook过程或者目的窗口。
Hook Procedures(hook过程)
为了利用特殊的hook类型,开发者提供了hook过程,使用SetWindowsHookEx函数来把hook过程安装到关联的hook链。hook过程必须按照以下的语法: LRESULT CALLBACK HookProc( int nCode, WPARAM wParam, LPARAM lParam ); HookProc是应用程序定义的名字。 nCode参数是hook代码,hook过程使用这个参数来确定任务。这个参数的值依赖于hook类型,每一种hook都有自己的hook代码特征字符集。wParam和lParam参数的值依赖于hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。 SetWindowsHookEx函数总是在hook链的开头安装hook过程。当指定类型的hook监视的事件发生时,系统就调用与这个hook关联的hook链的开头的hook过程。每一个hook链中的hook过程都决定是否把这个事件传递到下一个hook过程。hook过程传递事件到下一个hook过程需要调用CallNextHookEx函数。 有些类型hook的hook过程只能监视消息,不管是否调用了CallNextHookEx函数,系统都把消息传递到每一个hook过程。 全局hook监视同一桌面的所有线程。而特定线程的hook只能监视单独的线程。全局hook过程可以被同一桌面的任何应用程序调用,就象调用线程一样,所以这个过程必须和DLL模块分开。特定线程hook过程只可以被相关线程调用。只有在有调试目的的时候才使用全局hook,应该避免使用,全局hook损害了系统性能。
Hook Types
每一种类型的hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的hook类型。
WH_CALLWNDPROC and WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC and WH_CALLWNDPROCRET Hook使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC hook过程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook过程。 WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到hook过程。CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。
WH_CBT Hook
在以下事件之前,系统都会调用WH_CBT Hook过程,这些事件包括:激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;完成系统指令;来自系统消息队列中的移动鼠标,键盘事件;设置输入焦点事件;同步系统消息队列事件。hook过程的返回值确定系统是否允许或者防止这些操作中的一个。
WH_DEBUG Hook
在系统调用系统中与其他hook关联的hook过程之前,系统会调用WH_DEBUG Hook过程。你可以使用这个hook来决定是否允许系统调用与其他hook关联的hook过程。
WH_FOREGROUNDIDLE Hook
当应用程序的前景线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前景线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook过程。
WH_GETMESSAGE Hook
应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。
WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个hook回放通过使用WH_JOURNALRECORD hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK hook已经安装,正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYBACK hook是全局hook,它不能象线程特定hook一样使用。WH_JOURNALPLAYBACK hook返回超时值,这个值告诉系统在处理来自回放hook当前消息之前需要等待多长时间(毫秒)。这就使hook可以控制实时事件的回放。
WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。WH_JOURNALRECORD hook是全局hook,它不能象线程特定hook一样使用。
WH_KEYBOARD Hook
在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个hook来监视输入到消息队列中的键盘消息。
WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。
WH_MOUSE Hook
WH_MOUSE Hook监视从GetMessage or PeekMessage function返回的鼠标消息。使用这个hook监视输入到消息队列中的鼠标消息。
WH_MOUSE_LL Hook
WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。
WH_MSGFILTER and WH_SYSMSGFILTER Hooks
WH_MSGFILTER and WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了hook过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。 WH_MSGFILTER and WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。 通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
WH_SHELL Hook
外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook过程。 按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。
3.Using Hooks
Installing and Releasing Hook Procedures 可以使用SetWindowsHookEx function安装hook过程并且指定hook类型,指定是否需要把hook过程与所有线程关联,或者关联指定的线程,并且指向hook过程入口点。 必须把全局hook过程放进DLL,以和应用程序安装的hook过程分开。在应用程序安装hook过程之前,它必须有一个指向DLL模块的句柄。为了得到这个句柄,可以在调用LoadLibrary函数时使用DLL名字参数。在得到这个句柄以后,可以调用GetProcAddress函数来得到hook过程的指针。最后,使用SetWindowsHookEx函数安装hook过程地址进应用程序hook链。这个过程可以用下面的事例说明:
HOOKPROC hkprcSysMsg; static HINSTANCE hinstDLL; static HHOOK hhookSysMsg; hinstDLL = LoadLibrary((LPCTSTR) "c:\\windows\\sysmsg.dll"); file://loading DLL hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, "SysMessageProc"); file://get address hhookSysMsg = SetWindowsHookEx(WH_SYSMSGFILTER,hkprcSysMsg,hinstDLL,0); file://install hook 当应用程序不再需要与特定线程相关hook时,需要调用UnhookWindowsHookEx函数删除相关的hook过程。对于全局hook,也需要调用UnhookWindowsHookEx函数,但是这个函数不能释放DLL包含的hook过程。这是因为全局hook过程是被所有应用程序进程调用的,这就导致了所有的进程都隐性的调用了LoadLibrary函数。所以必须调用FreeLibrary函数释放DLL。
Monitoring System Events 下面的例子使用了不同的特定线程hook过程去监视系统事件。它示范了怎样使用下面的hook过程去处理事件: WH_CALLWNDPROC WH_CBT WH_DEBUG WH_GETMESSAGE WH_KEYBOARD WH_MOUSE WH_MSGFILTER 用户可以通过使用菜单安装或者移走hook过程。当hook过程已经安装并且过程监视的时间发生时,hook过程将在应用程序主窗口客户区写出事件信息。原代码如下:
#define NUMHOOKS 7 // Global variables typedef struct _MYHOOKDATA { int nType; HOOKPROC hkprc; HHOOK hhook; } MYHOOKDATA; MYHOOKDATA myhookdata[NUMHOOKS]; LRESULT WINAPI MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam) { static BOOL afHooks[NUMHOOKS]; int index; static HMENU hmenu; switch (uMsg) { case WM_CREATE: // Save the menu handle. hmenu = GetMenu(hwndMain); // Initialize structures with hook data. The menu-item // identifiers are defined as 0 through 6 in the // header file. They can be used to identify array // elements both here and during the WM_COMMAND // message. myhookdata[IDM_CALLWNDPROC].nType = WH_CALLWNDPROC; myhookdata[IDM_CALLWNDPROC].hkprc = CallWndProc; myhookdata[IDM_CBT].nType = WH_CBT; myhookdata[IDM_CBT].hkprc = CBTProc; myhookdata[IDM_DEBUG].nType = WH_DEBUG; myhookdata[IDM_DEBUG].hkprc = DebugProc; myhookdata[IDM_GETMESSAGE].nType = WH_GETMESSAGE; myhookdata[IDM_GETMESSAGE].hkprc = GetMsgProc; myhookdata[IDM_KEYBOARD].nType = WH_KEYBOARD; myhookdata[IDM_KEYBOARD].hkprc = KeyboardProc; myhookdata[IDM_MOUSE].nType = WH_MOUSE; myhookdata[IDM_MOUSE].hkprc = MouseProc; myhookdata[IDM_MSGFILTER].nType = WH_MSGFILTER; myhookdata[IDM_MSGFILTER].hkprc = MessageProc; // Initialize all flags in the array to FALSE. memset(afHooks, FALSE, sizeof(afHooks)); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { // The user selected a hook command from the menu. case IDM_CALLWNDPROC: case IDM_CBT: case IDM_DEBUG: case IDM_GETMESSAGE: case IDM_KEYBOARD: case IDM_MOUSE: case IDM_MSGFILTER: // Use the menu-item identifier as an index // into the array of structures with hook data. index = LOWORD(wParam); // If the selected type of hook procedure isn't // installed yet, install it and check the // associated menu item. if (!afHooks[index]) { myhookdata[index].hhook = SetWindowsHookEx( myhookdata[index].nType, myhookdata[index].hkprc, (HINSTANCE) NULL, GetCurrentThreadId()); CheckMenuItem(hmenu, index, MF_BYCOMMAND | MF_CHECKED); afHooks[index] = TRUE; } // If the selected type of hook procedure is // already installed, remove it and remove the // check mark from the associated menu item. else { UnhookWindowsHookEx(myhookdata[index].hhook); CheckMenuItem(hmenu, index, MF_BYCOMMAND | MF_UNCHECKED); afHooks[index] = FALSE; } default: return (DefWindowProc(hwndMain, uMsg, wParam, lParam)); } break; // // Process other messages. // default: return DefWindowProc(hwndMain, uMsg, wParam, lParam); } return NULL; } /**************************************************************** WH_CALLWNDPROC hook procedure ****************************************************************/ LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szCWPBuf[256]; CHAR szMsg[16]; HDC hdc; static int c = 0; int cch; if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode, wParam, lParam); // Call an application-defined function that converts a message // constant to a string and copies it to a buffer. LookUpTheMessage((PMSG) lParam, szMsg); hdc = GetDC(hwndMain); switch (nCode) { case HC_ACTION: cch = wsprintf(szCWPBuf, "CALLWNDPROC - tsk: %ld, msg: %s, %d times ", wParam, szMsg, c++); TextOut(hdc, 2, 15, szCWPBuf, cch); break; default: break; } ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode, wParam, lParam); } /**************************************************************** WH_GETMESSAGE hook procedure ****************************************************************/ LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szMSGBuf[256]; CHAR szRem[16]; CHAR szMsg[16]; HDC hdc; static int c = 0; int cch; if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode, wParam, lParam); switch (nCode) { case HC_ACTION: switch (wParam) { case PM_REMOVE: lstrcpy(szRem, "PM_REMOVE"); break; case PM_NOREMOVE: lstrcpy(szRem, "PM_NOREMOVE"); break; default: lstrcpy(szRem, "Unknown"); break; } // Call an application-defined function that converts a // message constant to a string and copies it to a // buffer. LookUpTheMessage((PMSG) lParam, szMsg); hdc = GetDC(hwndMain); cch = wsprintf(szMSGBuf, "GETMESSAGE - wParam: %s, msg: %s, %d times ", szRem, szMsg, c++); TextOut(hdc, 2, 35, szMSGBuf, cch); break; default: break; } ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode, wParam, lParam); } /**************************************************************** WH_DEBUG hook procedure ****************************************************************/ LRESULT CALLBACK DebugProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; HDC hdc; static int c = 0; int cch; if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[DEBUG].hhook, nCode, wParam, lParam); hdc = GetDC(hwndMain); switch (nCode) { case HC_ACTION: cch = wsprintf(szBuf, "DEBUG - nCode: %d, tsk: %ld, %d times ", nCode,wParam, c++); TextOut(hdc, 2, 55, szBuf, cch); break; default: break; } ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[DEBUG].hhook, nCode, wParam, lParam); } /**************************************************************** WH_CBT hook procedure ****************************************************************/ LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; CHAR szCode[128]; HDC hdc; static int c = 0; int cch; if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam, lParam); hdc = GetDC(hwndMain); switch (nCode) { case HCBT_ACTIVATE: lstrcpy(szCode, "HCBT_ACTIVATE"); break; case HCBT_CLICKSKIPPED: lstrcpy(szCode, "HCBT_CLICKSKIPPED"); break; case HCBT_CREATEWND: lstrcpy(szCode, "HCBT_CREATEWND"); break; case HCBT_DESTROYWND: lstrcpy(szCode, "HCBT_DESTROYWND"); break; case HCBT_KEYSKIPPED: lstrcpy(szCode, "HCBT_KEYSKIPPED"); break; case HCBT_MINMAX: lstrcpy(szCode, "HCBT_MINMAX"); break; case HCBT_MOVESIZE: lstrcpy(szCode, "HCBT_MOVESIZE"); break; case HCBT_QS: lstrcpy(szCode, "HCBT_QS"); break; case HCBT_SETFOCUS: lstrcpy(szCode, "HCBT_SETFOCUS"); break; case HCBT_SYSCOMMAND: lstrcpy(szCode, "HCBT_SYSCOMMAND"); break; default: lstrcpy(szCode, "Unknown"); break; } cch = wsprintf(szBuf, "CBT - nCode: %s, tsk: %ld, %d times ", szCode, wParam, c++); TextOut(hdc, 2, 75, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam, lParam); } /**************************************************************** WH_MOUSE hook procedure ****************************************************************/ LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; CHAR szMsg[16]; HDC hdc; static int c = 0; int cch; if (nCode < 0) // do not process the message return CallNextHookEx(myhookdata[MOUSE].hhook, nCode, wParam, lParam); // Call an application-defined function that converts a message // constant to a string and copies it to a buffer. LookUpTheMessage((PMSG) lParam, szMsg); hdc = GetDC(hwndMain); cch = wsprintf(szBuf, "MOUSE - nCode: %d, msg: %s, x: %d, y: %d, %d times ", nCode, szMsg, LOWORD(lParam), HIWORD(lParam), c++); TextOut(hdc, 2, 95, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[MOUSE].hhook, nCode, wParam, lParam); } /**************************************************************** WH_KEYBOARD hook procedure ****************************************************************/ LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; HDC hdc; static int c = 0; int cch; if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode, wParam, lParam); hdc = GetDC(hwndMain); cch = wsprintf(szBuf, "KEYBOARD - nCode: %d, vk: %d, %d times ", nCode, wParam, c++); TextOut(hdc, 2, 115, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode, wParam, lParam); } /**************************************************************** WH_MSGFILTER hook procedure ****************************************************************/ LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; CHAR szMsg[16]; CHAR szCode[32]; HDC hdc; static int c = 0; int cch; if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode, wParam, lParam); switch (nCode) { case MSGF_DIALOGBOX: lstrcpy(szCode, "MSGF_DIALOGBOX"); break; case MSGF_MENU: lstrcpy(szCode, "MSGF_MENU"); break; case MSGF_SCROLLBAR: lstrcpy(szCode, "MSGF_SCROLLBAR"); break; default: wsprintf(szCode, "Unknown: %d", nCode); break; } // Call an application-defined function that converts a message // constant to a string and copies it to a buffer. LookUpTheMessage((PMSG) lParam, szMsg); hdc = GetDC(hwndMain); cch = wsprintf(szBuf, "MSGFILTER nCode: %s, msg: %s, %d times ", szCode, szMsg, c++); TextOut(hdc, 2, 135, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode, wParam, lParam); }
未完待续。 
|