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;
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
未完待續。