C++鍵盤鉤子
C++鍵盤鉤子
Windows系統是建立在事件驅動的機制上的,整個系統都是通過訊息傳遞來實現的。而鉤子是Windows系統中非常重要的系統介面,用它可以截獲並處理送給其他應用程式的訊息,來完成普通應用程式難以實現的功能。鉤子可以監視系統或程序中的各種事件訊息,截獲發往目標視窗的訊息並進行處理。這樣,我們就可以在系統中安裝自定義的鉤子,監視系統中特定事件的發生,完成特定的功能,比如截獲鍵盤、滑鼠的輸入,螢幕取詞,日誌監視等等。可見,利用鉤子可以實現許多特殊而有用的功能。因此,對於高階程式設計人員來說,掌握鉤子的程式設計方法是很有必要的。
鉤子型別
按事件分類,有如下的幾種常用型別:
- 鍵盤鉤子和低階鍵盤鉤子可以監視各種鍵盤訊息;
- 滑鼠鉤子和低階滑鼠鉤子可以監視各種滑鼠訊息;
- 外殼鉤子可以監視各種Shell事件訊息,比如啟動和關閉應用程式;
- 日誌鉤子可以記錄從系統訊息佇列中取出的各種事件訊息;
- 視窗過程鉤子監視所有從系統訊息佇列發往目標視窗的訊息。
此外,還有一些特定事件的鉤子提供給我們使用,不一一列舉。
下面描述常用的Hook型別:
1. WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以監視傳送到視窗過程的訊息。系統在訊息傳送到接收視窗過程之前呼叫WH_CALLWNDPROC Hook
2. WH_CBT Hook
在以下事件之前,系統都會呼叫WH_CBT Hook子程,這些事件包括:
- 啟用,建立,銷燬,最小化,最大化,移動,改變尺寸等視窗事件;
- 完成系統指令;
- 來自系統訊息佇列中的移動滑鼠,鍵盤事件;
- 設定輸入焦點事件;
- 同步系統訊息佇列事件。
Hook子程的返回值確定系統是否允許或者防止這些操作中的一個。
3. WH_DEBUG Hook
在系統呼叫系統中與其他Hook關聯的Hook子程之前,系統會呼叫WH_DEBUG Hook子程。你可以使用這個Hook來決定是否允許系統呼叫與其他Hook關聯的Hook子程。
4. WH_FOREGROUNDIDLE Hook
當應用程式的前臺執行緒處於空閒狀態時,可以使用WH_FOREGROUNDIDLE Hook執行低優先順序任務。當應用程式的前臺執行緒大概要變成空閒狀態時,系統就會呼叫WH_FOREGROUNDIDLE Hook子程。
5. WH_GETMESSAGE Hook
應用程式使用WH_GETMESSAGE Hook來監視從GetMessage或PeekMessage函式返回的訊息。你可以使用WH_GETMESSAGE Hook去監視滑鼠和鍵盤輸入,以及其他傳送到訊息佇列中的訊息。
6. WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook使應用程式可以插入訊息到系統訊息佇列。可以使用這個Hook回放通過使用WH_JOURNALRECORD Hook記錄下來的連續的滑鼠和鍵盤事件。只要WH_JOURNALPLAYBACK Hook已經安裝,正常的滑鼠和鍵盤事件就是無效的。WH_JOURNALPLAYBACK Hook是全域性Hook,它不能像執行緒特定Hook一樣使用。WH_JOURNALPLAYBACK Hook返回超時值,這個值告訴系統在處理來自回放Hook當前訊息之前需要等待多長時間(毫秒)。這就使Hook可以控制實時事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程地址空間(估計按鍵精靈是用這個hook做的)。
7. WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook用來監視和記錄輸入事件。典型的,可以使用這個Hook記錄連續的滑鼠和鍵盤事件,然後通過使用WH_JOURNALPLAYBACK Hook來回放。WH_JOURNALRECORD Hook是全域性Hook,它不能像執行緒特定Hook一樣使用。WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程地址空間。
8. WH_KEYBOARD Hook
在應用程式中,WH_KEYBOARD Hook用來監視WM_KEYDOWN和WM_KEYUP訊息,這些訊息通過GetMessage或PeekMessage函式返回。可以使用這個Hook來監視輸入到訊息佇列中的鍵盤訊息。
9. WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook監視輸入到執行緒訊息佇列中的鍵盤訊息。
10. WH_MOUSE Hook
WH_MOUSE Hook監視從GetMessage或PeekMessage函式返回的滑鼠訊息。使用這個Hook監視輸入到訊息佇列中的滑鼠訊息。
11. WH_MOUSE_LL Hook
WH_MOUSE_LL Hook監視輸入到執行緒訊息佇列中的滑鼠訊息。
12. WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks
WH_MSGFILTER和WH_SYSMSGFILTER Hooks使我們可以監視選單,滾動條,訊息框,對話方塊訊息並且發現使用者使用ALT+TAB or ALT+ESC 組合鍵切換視窗。WH_MSGFILTER Hook只能監視傳遞到選單,滾動條,訊息框的訊息,以及傳遞到通過安裝了Hook子程的應用程式建立的對話方塊的訊息。WH_SYSMSGFILTER Hook監視所有應用程式訊息。WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式迴圈期間過濾訊息,這等價於在主訊息迴圈中過濾訊息。通過呼叫CallMsgFilter function可以直接的呼叫WH_MSGFILTER Hook。通過使用這個函式,應用程式能夠在模式迴圈期間使用相同的程式碼去過濾訊息,如同在主訊息迴圈裡一樣。
13. WH_SHELL Hook
外殼應用程式可以使用WH_SHELL Hook去接收重要的通知。當外殼應用程式是啟用的並且當頂層視窗建立或者銷燬時,系統呼叫WH_SHELL Hook子程。
WH_SHELL共有5鍾情況:
- 只要有個top-level、unowned視窗被產生、起作用、或是被摧毀;
- 當Taskbar需要重畫某個按鈕;
- 當系統需要顯示關於Taskbar的一個程式的最小化形式;
- 當目前的鍵盤佈局狀態改變;
- 當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程式)。
按照慣例,外殼應用程式都不接收WH_SHELL訊息。所以,在應用程式能夠接收WH_SHELL訊息之前,應用程式必須呼叫SystemParametersInfo function註冊它自己。
以上是13種常用的hook型別!
執行緒鉤子和系統鉤子
- 執行緒鉤子監視指定執行緒的事件訊息。
- 系統鉤子監視系統中的所有執行緒的事件訊息。因為系統鉤子會影響系統中所有的應用程式,所以鉤子函式必須放在獨立的動態連結庫(DLL)中。這是系統鉤子和執行緒鉤子很大的不同之處。
幾點需要說明的地方:
- 如果對於同一事件(如滑鼠訊息)既安裝了執行緒鉤子又安裝了系統鉤子,那麼系統會自動先呼叫執行緒鉤子,然後呼叫系統鉤子。
- 對同一事件訊息可安裝多個鉤子處理過程,這些鉤子處理過程形成了鉤子鏈。當前鉤子處理結束後應把鉤子資訊傳遞給下一個鉤子函式。而且最近安裝的鉤子放在鏈的開始,而最早安裝的鉤子放在最後,也就是後加入的先獲得控制權。
- 鉤子特別是系統鉤子會消耗訊息處理時間,降低系統性能。只有在必要的時候才安裝鉤子,在使用完畢後要及時解除安裝。
編寫鉤子程式
編寫鉤子程式的步驟分為三步:定義鉤子函式、安裝鉤子和解除安裝鉤子。
1. 定義鉤子函式
鉤子函式是一種特殊的回撥函式。鉤子監視的特定事件發生後,系統會呼叫鉤子函式進行處理。不同事件的鉤子函式的形式是各不相同的。下面以滑鼠鉤子函式舉例說明鉤子函式的原型:
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam)
引數wParam和 lParam包含所鉤訊息的資訊,比如滑鼠位置、狀態,鍵盤按鍵等。nCode包含有關訊息本身的資訊,比如是否從訊息佇列中移出。
我們先在鉤子函式中實現自定義的功能,然後呼叫函式 CallNextHookEx.把鉤子資訊傳遞給鉤子鏈的下一個鉤子函式。CallNextHookEx.的原型如下:
LRESULT CallNextHookEx(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam )
引數hhk是鉤子控制代碼。引數nCode、引數wParam和引數lParam是鉤子函式。
當然也可以通過直接返回TRUE來丟棄該訊息,就阻止了該訊息的傳遞。
2. 安裝鉤子
在程式初始化的時候,呼叫函式SetWindowsHookEx安裝鉤子。其函式原型為:
HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
- 引數idHook表示鉤子型別,它是和鉤子函式型別一一對應的。比如,WH_KEYBOARD表示安裝的是鍵盤鉤子,WH_MOUSE表示是滑鼠鉤子等等。
- 引數Lpfn是鉤子函式的地址。
- 引數HMod是鉤子函式所在的例項的控制代碼。對於執行緒鉤子,該引數為NULL;對於系統鉤子,該引數為鉤子函式所在的DLL控制代碼。
- 引數dwThreadId指定鉤子所監視的執行緒的執行緒號。對於全域性鉤子,該引數為NULL。
- 引數SetWindowsHookEx返回所安裝的鉤子控制代碼。
3. 解除安裝鉤子
當不再使用鉤子時,必須及時解除安裝。簡單地呼叫函式BOOL UnhookWindowsHookEx( HHOOK hhk)即可。
值得注意的是執行緒鉤子和系統鉤子的鉤子函式的位置有很大差別。執行緒鉤子一般在當前執行緒或當前執行緒派生的執行緒內,而系統鉤子必須放在獨立的動態連結庫中,實現起來要麻煩一些。
執行緒鉤子的程式設計例項
按照上面介紹的方法實現一個執行緒級的滑鼠鉤子。鉤子跟蹤當前視窗滑鼠移動的位置變化資訊。並輸出到視窗。
1. 在VC++6.0中利用MFC APPWizard(EXE)生成一個不使用文件/視結構的單文件應用mousehook。開啟childview.cpp檔案,加入全域性變數:
HHOOK hHook;//滑鼠鉤子控制代碼
CPoint point;//滑鼠位置資訊
CChildView *pView;// 滑鼠鉤子函式用到的輸出視窗指標
在CChildView::OnPaint()新增如下程式碼:
CPaintDC dc(this);
char str[256];
sprintf(str,“x=%d,y=%d",point.x,point.y); // 構造字串
dc.TextOut(0,0,str); //顯示字串
2. childview.cpp檔案中定義全域性的滑鼠鉤子函式。
LRESULT CALLBACK MouseProc (int nCode, WPARAM wParam, LPARAM lParam)
{//是滑鼠移動訊息
if(wParam==WM_MOUSEMOVE||wParam ==WM_NCMOUSEMOVE)
{
point=((MOUSEHOOKSTRUCT *)lParam)->pt; //取滑鼠資訊
pView->Invalidate(); //視窗重畫
}
return CallNextHookEx(hHook,nCode,wParam,lParam); //傳遞鉤子資訊
}
3. CChildView類的建構函式中安裝鉤子。
CChildView::CChildView()
{
pView=this;//獲得輸出視窗指標
hHook=SetWindowsHookEx(WH_MOUSE,MouseProc,0,GetCurrentThreadId());
}
(4)CChildView類的解構函式中解除安裝鉤子。
CChildView::~CChildView()
{
if(hHook)
UnhookWindowsHookEx(hHook);
}