1. 程式人生 > >C++鍵盤鉤子

C++鍵盤鉤子

C++鍵盤鉤子

Windows系統是建立在事件驅動的機制上的,整個系統都是通過訊息傳遞來實現的。而鉤子是Windows系統中非常重要的系統介面,用它可以截獲並處理送給其他應用程式的訊息,來完成普通應用程式難以實現的功能。鉤子可以監視系統或程序中的各種事件訊息,截獲發往目標視窗的訊息並進行處理。這樣,我們就可以在系統中安裝自定義的鉤子,監視系統中特定事件的發生,完成特定的功能,比如截獲鍵盤、滑鼠的輸入,螢幕取詞,日誌監視等等。可見,利用鉤子可以實現許多特殊而有用的功能。因此,對於高階程式設計人員來說,掌握鉤子的程式設計方法是很有必要的。

鉤子型別

按事件分類,有如下的幾種常用型別:

  1. 鍵盤鉤子和低階鍵盤鉤子可以監視各種鍵盤訊息;
  2. 滑鼠鉤子和低階滑鼠鉤子可以監視各種滑鼠訊息;
  3. 外殼鉤子可以監視各種Shell事件訊息,比如啟動和關閉應用程式;
  4. 日誌鉤子可以記錄從系統訊息佇列中取出的各種事件訊息;
  5. 視窗過程鉤子監視所有從系統訊息佇列發往目標視窗的訊息。

此外,還有一些特定事件的鉤子提供給我們使用,不一一列舉。

下面描述常用的Hook型別:

1. WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks

WH_CALLWNDPROCWH_CALLWNDPROCRET Hooks使你可以監視傳送到視窗過程的訊息。系統在訊息傳送到接收視窗過程之前呼叫WH_CALLWNDPROC Hook

子程,並且在視窗過程處理完訊息之後呼叫WH_CALLWNDPROCRET Hook子程。WH_CALLWNDPROCRET Hook傳遞指標到CWPRETSTRUCT結構,再傳遞到Hook子程。CWPRETSTRUCT結構包含了來自處理訊息的視窗過程的返回值,同樣也包括了與這個訊息關聯的訊息引數。

2. WH_CBT Hook

在以下事件之前,系統都會呼叫WH_CBT Hook子程,這些事件包括:

  1. 啟用,建立,銷燬,最小化,最大化,移動,改變尺寸等視窗事件;
  2. 完成系統指令;
  3. 來自系統訊息佇列中的移動滑鼠,鍵盤事件;
  4. 設定輸入焦點事件;
  5. 同步系統訊息佇列事件。

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來監視從GetMessagePeekMessage函式返回的訊息。你可以使用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_JOURNALPLAYBACKsystem-wide local hooks,它們不會被注射到任何行程地址空間(估計按鍵精靈是用這個hook做的)。

7. WH_JOURNALRECORD Hook

WH_JOURNALRECORD Hook用來監視和記錄輸入事件。典型的,可以使用這個Hook記錄連續的滑鼠和鍵盤事件,然後通過使用WH_JOURNALPLAYBACK Hook來回放。WH_JOURNALRECORD Hook是全域性Hook,它不能像執行緒特定Hook一樣使用。WH_JOURNALRECORDsystem-wide local hooks,它們不會被注射到任何行程地址空間。

8. WH_KEYBOARD Hook

在應用程式中,WH_KEYBOARD Hook用來監視WM_KEYDOWNWM_KEYUP訊息,這些訊息通過GetMessagePeekMessage函式返回。可以使用這個Hook來監視輸入到訊息佇列中的鍵盤訊息。

9. WH_KEYBOARD_LL Hook

WH_KEYBOARD_LL Hook監視輸入到執行緒訊息佇列中的鍵盤訊息。

10. WH_MOUSE Hook

WH_MOUSE Hook監視從GetMessagePeekMessage函式返回的滑鼠訊息。使用這個Hook監視輸入到訊息佇列中的滑鼠訊息。

11. WH_MOUSE_LL Hook

WH_MOUSE_LL Hook監視輸入到執行緒訊息佇列中的滑鼠訊息。

12. WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks

WH_MSGFILTERWH_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鍾情況:

  1. 只要有個top-levelunowned視窗被產生、起作用、或是被摧毀;
  2. Taskbar需要重畫某個按鈕;
  3. 當系統需要顯示關於Taskbar的一個程式的最小化形式;
  4. 當目前的鍵盤佈局狀態改變;
  5. 當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程式)。

按照慣例,外殼應用程式都不接收WH_SHELL訊息。所以,在應用程式能夠接收WH_SHELL訊息之前,應用程式必須呼叫SystemParametersInfo function註冊它自己。

以上是13種常用的hook型別!

執行緒鉤子和系統鉤子

  1. 執行緒鉤子監視指定執行緒的事件訊息。
  2. 系統鉤子監視系統中的所有執行緒的事件訊息。因為系統鉤子會影響系統中所有的應用程式,所以鉤子函式必須放在獨立的動態連結庫(DLL)中。這是系統鉤子和執行緒鉤子很大的不同之處。

幾點需要說明的地方:

  1. 如果對於同一事件(如滑鼠訊息)既安裝了執行緒鉤子又安裝了系統鉤子,那麼系統會自動先呼叫執行緒鉤子,然後呼叫系統鉤子。
  2. 對同一事件訊息可安裝多個鉤子處理過程,這些鉤子處理過程形成了鉤子鏈。當前鉤子處理結束後應把鉤子資訊傳遞給下一個鉤子函式。而且最近安裝的鉤子放在鏈的開始,而最早安裝的鉤子放在最後,也就是後加入的先獲得控制權。
  3. 鉤子特別是系統鉤子會消耗訊息處理時間,降低系統性能。只有在必要的時候才安裝鉤子,在使用完畢後要及時解除安裝。

編寫鉤子程式

編寫鉤子程式的步驟分為三步:定義鉤子函式、安裝鉤子和解除安裝鉤子。

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 )

  1. 引數idHook表示鉤子型別,它是和鉤子函式型別一一對應的。比如,WH_KEYBOARD表示安裝的是鍵盤鉤子,WH_MOUSE表示是滑鼠鉤子等等。
  2. 引數Lpfn是鉤子函式的地址。
  3. 引數HMod是鉤子函式所在的例項的控制代碼。對於執行緒鉤子,該引數為NULL;對於系統鉤子,該引數為鉤子函式所在的DLL控制代碼。
  4. 引數dwThreadId指定鉤子所監視的執行緒的執行緒號。對於全域性鉤子,該引數為NULL
  5. 引數SetWindowsHookEx返回所安裝的鉤子控制代碼。

3. 解除安裝鉤子

當不再使用鉤子時,必須及時解除安裝。簡單地呼叫函式BOOL UnhookWindowsHookEx( HHOOK hhk)即可。

值得注意的是執行緒鉤子和系統鉤子的鉤子函式的位置有很大差別。執行緒鉤子一般在當前執行緒或當前執行緒派生的執行緒內,而系統鉤子必須放在獨立的動態連結庫中,實現起來要麻煩一些。

執行緒鉤子的程式設計例項

按照上面介紹的方法實現一個執行緒級的滑鼠鉤子。鉤子跟蹤當前視窗滑鼠移動的位置變化資訊。並輸出到視窗。

1. VC++6.0中利用MFC APPWizardEXE)生成一個不使用文件/視結構的單文件應用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);

}