對鉤子的理解
說起鉤子,很多人都想起它的形狀,根據形狀去想象windows的鉤子,會陷入誤區。Windows的鉤子,是沒有形狀的,之所以叫鉤子,並不是說它的形狀象鉤子,而是它的作用和鉤子一樣:就是用來“掛靠”的。比如有個包工頭想去接個項目,但是他沒有公司,拿不出相關的證件,怎麼辦呢?那就得找個公司掛靠,成立個子公司(subclass技術)之類,讓那個公司幫他出資源,出相關的證件之類......windows的鉤子和這很相似。比如我們常常想訪問另一個進程的一些資源(常見的獲取編輯框的密碼),怎麼辦呢?用鉤子來做掛靠,成爲另一個進程的一個分子,那樣你就可以在一定規則下(掛靠的當然都會有限制了)訪問、控制該進程的資源了。鉤子不但可以掛靠別的進程,本進程也可以用鉤子去“掛靠”和控制。
有關鉤子的API
(有關API的更詳細和權威的介紹,請參考MSDN)
1、安裝鉤子的API:SetWindowsHookEx(),要使用鉤子,首先得安裝鉤子。任何一個鉤子都由系統來維護一個指針列表(鉤子鏈表),其指針指向鉤子的各個處理函數。最近安裝的鉤子放在鏈的開始,最早安裝的鉤子則放在最後,當鉤子監視的消息出現時,操作系統調用鏈表開始處的第一個鉤子處理函數進行處理,也就是說最後加入的鉤子優先獲得控制權。呵呵,最後這句話就是提示我們怎麼反鉤子的。
HHOOK SetWindowsHookEx(int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId)
其中:參數idHook 指定了鉤子的類型,總共有如下13種:
WH_CALLWNDPROC 系統將消息發送到指定窗口之前的"鉤子"
WH_CALLWNDPROCRET 消息已經在窗口中處理的"鉤子"
WH_CBT 基於計算機培訓的"鉤子"
WH_DEBUG 差錯"鉤子"
WH_FOREGROUNDIDLE 前臺空閒窗口"鉤子"
WH_GETMESSAGE 接收消息投遞的"鉤子"
WH_JOURNALPLAYBACK 回放以前通過WH_JOURNALRECORD"鉤子"記錄的輸入消息
WH_JOURNALRECORD 輸入消息記錄"鉤子"
WH_KEYBOARD 鍵盤消息"鉤子"
WH_MOUSE 鼠標消息"鉤子"
WH_MSGFILTER 對話框、消息框、菜單或滾動條輸入消息"鉤子"
WH_SHELL 外殼"鉤子"
WH_SYSMSGFILTER 系統消息"鉤子"
這樣,你就可以用各種各樣的鉤子去做掛靠訪問了,比如你可以用魚勾去釣魚,用衣服鉤子去鉤衣服,但不要用錯……
參數lpfn爲指向鉤子處理函數的指針,即回調函數的首地址;參數hMod則標識了鉤子處理函數所處模塊的句柄;第四個參數dwThreadId 指定被監視的線程,如果明確指定了某個線程的ID就只監視該線程,此時的鉤子即爲線程鉤子;如果該參數被設置爲0,則表示此鉤子爲監視系統所有線程的全局鉤子。此函數在執行完後將返回一個鉤子句柄。
對於全局鉤子,要使用動態連接庫來實現,因爲你的鉤子需要被其他進程和系統調用,如果放在你的進程中,估計別的進程也得產生個鉤子來訪問你的鉤子了J,對於一些線程鉤子,雖然不是全局鉤子,建議也使用動態連接庫。
2、傳遞鉤子的API:CallNextHookEx( )。在安裝鉤子以後,鉤子會對相關的資源進行訪問控制,如果希望自己處理完相應的事物後,將系統交還給系統和原來的進程,就需要調用CallNextHookEx( )。一些反鉤子的代碼常常不調用該函數,這樣外掛程序安裝的鉤子就不會被運行。
LRESULT CallNextHookEx(HHOOK hhk,int nCode,WPARAM wParam,LPARAM lParam);
其中,參數hhk爲由SetWindowsHookEx()函數返回的當前鉤子句柄;參數nCode爲傳給鉤子過程的事件代碼;參數wParam和lParam 則爲傳給鉤子處理函數的參數值,其具體含義同設置的鉤子類型有關。
3、卸載鉤子的API:SetWindowsHookEx()。由於鉤子對系統的性能有影響,在安裝使用完鉤子以後,就需要把鉤子卸載。
BOOL UnhookWindowsHookEx(HHOOK hhk);
其中hhk是使用SetWindowsHookEx()安裝鉤子時產生的返回值。
注意:windows的鉤子爲了提高系統的性能,採用了“copy-on-write”的技術,在SetWindowsHookEx()調用後並不馬上安裝鉤子。比如在設置WH_CALLWNDPROC鉤子時,鉤子並不馬上生效,而要在系統將消息發送到指定窗口的時候纔會正式安裝鉤子,纔會把鉤子掛靠在進程內(實際上不只是掛靠鉤子過程,還會把整個dll影射到進程內),才能訪問進程的資源,否則是個假鉤子,徒有虛名而已。
鉤子的簡單應用
這個例子不是我寫的,在網上找的,是個鍵盤鉤子。通過這個例子可以對鉤子和鉤子的有關API有比較深刻的認識。原來的連接在http://www.pconline.com.cn/pcedu/empolder/gj/vc/0403/340480.html可以找到,但是代碼編譯有問題。我作了些修改。
附代碼如下:
// test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD g_main_tid = 0;
HHOOK g_kb_hook = 0;
#define _WIN32_WINNT 0x0400
BOOL CALLBACK con_handler (DWORD)
{
PostThreadMessage (g_main_tid, WM_QUIT, 0, 0);
return TRUE;
};
//--add by pdg
#define WH_KEYBOARD_LL 13
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
//--add by pdg,由於編譯時認不到這兩個定義,因此此次重新做了定義
LRESULT CALLBACK kb_proc (int code, WPARAM w, LPARAM l)
{
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)l;
const char *info = NULL;
if (w == WM_KEYDOWN)
info = "key dn";
else if (w == WM_KEYUP)
info = "key up";
else if (w == WM_SYSKEYDOWN)
info = "sys key dn";
else if (w == WM_SYSKEYUP)
info = "sys key up";
printf ("%s - vkCode [%04x], scanCode [%04x]/n",
info, p->vkCode, p->scanCode);
// always call next hook
return CallNextHookEx (g_kb_hook, code, w, l);
};
int _tmain(int argc, _TCHAR* argv[])
{
g_main_tid = GetCurrentThreadId ();
SetConsoleCtrlHandler (&con_handler, TRUE);
g_kb_hook = SetWindowsHookEx (
WH_KEYBOARD_LL,
&kb_proc,
GetModuleHandle (NULL), // 不能爲NULL,否則失敗
0);
if (g_kb_hook == NULL)
{
fprintf (stderr,
"SetWindowsHookEx failed with error %d/n",
::GetLastError ());
return 0;
};
// 消息循環是必須的,想知道原因可以查msdn
MSG msg;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
};
UnhookWindowsHookEx (g_kb_hook);
return 0;
}