1. 程式人生 > >全域性鉤子函式之 SetWindowsHookEx

全域性鉤子函式之 SetWindowsHookEx

在windows作業系統中,如果我們想對鍵盤進行重定義,比如說按某鍵就可發直接上網,按某鍵可以直接關閉視窗等等,如何實現呢!在Visual C++中用常規class wizard方法是不可以實現的,這裡我們用兩種方法去實現它。

方法1:利用RegisterHotKey函獲數實現

程式實現原理:首先使用者預定一個熱鍵,無論該程式是前臺程式還是後臺程式,只要使用者按了這個鍵,就執行我們定義好的函式。程式中要對熱鍵訊息WM_HOTKEY進行捕獲,並通過訊息引數瞭解哪一個鍵被按下。
因為VC中的CLASSWIZARD中沒有對訊息WM_HOTKEY進行封裝,我們只有通過手工加入程式碼實現。該訊息的對映及處理。
  具體實現步驟如下:
l 1 用MFC AppWizard建立一個工程名為:HotKey基於Dialog base的對話方塊程式,點選finish。
l 2 宣告熱鍵訊息處理函式原型
  在HotKeyDlg.h中訊息對映宣告處(AFX_mSG字樣之後)加入如下語句(其中畫線部分是加入的程式碼):
// Generated message map functions
//{{AFX_MSG(CHotKeyDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
LRESULT OnHotKey(WPARAM wParam,LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
l 3 訊息與相應處理函式相關聯 在HotKeyDlg.Cpp中加入訊息對映巨集,使訊息與相應處理函式發生關係,加入如下語句(其中畫線部分是加入的程式碼):
BEGIN_MESSAGE_MAP(CHotKeyDlg, CDialog)
//{{AFX_MSG_MAP(CHotKeyDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(WM_HOTKEY,OnHotKey)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
l 4 為方便以後的操作預先在CHotKeyDlg類中利用CLASSWIZARD實現一個響應WM_CREATE和WM_DESTROY訊息的函式OnCreate( )與OnDestroy( )的框架,(利用CLASSWIZARD很容易實現,請參考有關VC的書籍,在此不再贅述)。
l 5 向系統登記熱鍵
  在OnCreate()函式中加入如下程式碼以向系統登記熱鍵,本例子的熱鍵設為ESC.
RegisterHotKey(m_hWnd,1001,NULL, VK_ESCAPE);
//函式引數請參考有關VC的書籍,在此不再贅述
l 6 處理熱鍵
在訊息處理函式OnHotKey()中對熱鍵進行處理,並可加入使用者希望執行的程式程式碼等:
(注下面程式碼是在HotKeyDlg.cpp中完全用手工加入的程式碼)
  LRESULT CHotKeyDlg::OnHotKey(WPARAM wParam,LPARAM lParam)
{
HWND hwnd;
hwnd=::GetForegroundWindow();
::PostMessage(hwnd,WM_CLOSE,0,0);
return 0;
}
l 7 程式執行完畢後解除熱鍵
在OnDestroy()中通過UnRegisterHotKey()解除熱鍵登記,釋放系統資源.
 UnregisterHotKey( m_hWnd, 1001);
l 8 編譯並執行程式
  執行程式後,無論何時只要按下熱鍵Esc後本程式便立關閉當前的視窗。
方法2:利用鍵盤鉤子函獲數實現
說到鉤子函式,可能對許多初學程式設計的人很陌生,我這裡就多說幾句:
WINDOW的訊息處理機制為了能在應用程式中監控系統的各種事件訊息,提供了掛接各種反調函式(HOOK)的功能。這種掛鉤函式(HOOK)類似擴充中斷驅動程式,掛鉤可以掛接多個反調函式構成一個掛接函式鏈。系統產生的各種訊息首先被送到各種掛接函式,掛接函式根據各自的功能對訊息進行監視、修改和控制等,然後交還控制權或將訊息傳遞給下一個掛接函式以致最終達到視窗函式。WINDOW系統的這種反調函式掛接方法雖然會略加影響到系統的執行效率,但在很多場合下是非常有用的,通過合理有效地利用鍵盤事件的掛鉤函式監控機制可以達到預想不到的良好效果。
l 1 在WINDOW下可進行掛接的過濾函式包括11種:
WH_CALLWNDPROC 視窗函式的過濾函式
WH_CBT 計算機培訓過濾函式
WH_DEBUG 除錯過濾函式
WH_GETMESSAGE 獲取訊息過濾函式
WH_HARDWARE 硬體訊息過濾函式
WH_JOURNALPLAYBACK 訊息重放過濾函式
WH_JOURNALRECORD 訊息記錄過濾函式
WH_MOUSE 滑鼠過濾函式
WH_MSGFILTER 訊息過濾函式
WH_SYSMSGFILTER 系統訊息過濾函式
WH_KEYBOARD 鍵盤過濾函式
WH_KEYBOARD_LL在WindowsNt4.0以上可用的鍵盤過濾函式
WH_MOUSE_LL 在WindowsNt4.0以上可用的滑鼠過濾函式
l 2 其中鍵盤過濾函式是最常用最有用的過濾函式型別,不管是哪一種型別的過濾函式,其掛接的基本方法都是相同的。WINDOW呼叫掛接的反調函式時總是先呼叫掛接鏈首的那個函式,因此必須將鍵盤掛鉤函式利用函式SetWindowsHookEx()將其掛接在函式鏈首。至於訊息是否傳遞給函式鏈的下一個函式是由每個具體函式功能確定的,如果訊息需要傳統給下一個函式,可呼叫API函式的CallNextHookEx()來實現,如果不傳遞直接返回即可。
l 在程式中可以利用函式SetWindowsHookEx()來掛接過濾函式,在掛接函式時必須指出該掛接函式的型別、函式的入口地址以及函式是全域性性的還是區域性性的,掛接函式的具體呼叫格式如下:
HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);
其中,第一個引數是鉤子的型別;第二個引數是鉤子函式的地址;第三個引數是包含鉤子函式的模組控制代碼;第四個引數指定監視的執行緒。如果指定確定的執行緒,即為執行緒專用鉤子;如果指定為空,即為全域性鉤子。其中,全域性鉤子函式必須包含在DLL(動態連結庫)中,而執行緒專用鉤子還可以包含在可執行檔案中。得到控制權的鉤子函式在完成對訊息的處理後,如果想要該訊息繼續傳遞,那麼它必須呼叫另外一個SDK中的API函式CallNextHookEx來傳遞它。鉤子函式也可以通過直接返回TRUE來丟棄該訊息,並阻止該訊息的傳遞。的確如果函式是全域性性的,那麼它必須放在一個DLL(動態連結庫)中,但是我發現在window 2000以上的版本中,不用寫 DLL(動態連結庫)就可以作出全域性性的鍵盤函式。用它可以做很多事情。UnhookWindowsHookEx(int idHook)函式即可實現對掛接鉤子的解除安裝。
l 我們開始寫程式吧!用MFC AppWizard建立一個工程名為: KBoardHook基於Dialog base的對話方塊程式,點選finish。
l 在KBoardHookDlg.cpp的最上邊加上
HHOOK hhkLowLevelKybd2000;
hhkLowLevelKybd2000為全域性變數。
l 為方便以後的操作預先在CKBoardHookDlg類中利用CLASSWIZARD實現一個響應WM_CREATE和WM_DESTROY訊息的函式OnCreate( )與OnDestroy( )的框架,
l 在OnCreate()函式中通過SetWindowsHookEx與系統掛起鉤子程式碼如下
hhkLowLevelKybd2000 = SetWindowsHookEx(WH_KEYBOARD_LL,
LowLevelKeyboardProc, AfxGetApp()->m_hInstance, 0);
l 在OnDestroy()中通過UnhookWindowsHookEx ()解除已經掛起鉤子,釋放系統資源, 程式碼如下: UnhookWindowsHookEx(hhkLowLevelKybd2000);
l 這時在KBoardHookDlg.h中宣告LowLevelKeyboardProc ,在class CKBoardHookDlg : public Cdialog的上面加入如下程式碼
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
// 當nCode為0(在winuser.h中有如下定義:#define HC_ACTION 0)時wParam, lParam才包含所應有的鍵盤資訊,wParam的值代表了鍵盤的訊息可以為WM_KEYDOWN
WM_KEYUP, lParam為指向KBDLLHOOKSTRUCT的指標。
l 再在KBoardHookDlg.cpp的最後加入如下程式碼(這此程式碼都是手工加入的,不能用ClassWzrd)
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
if (nCode == HC_ACTION) {
int vKey=LOBYTE(p->vkCode);
switch (wParam)
{
case WM_KEYDOWN:
{
if(vKey==27)
{
HWND hwnd;
hwnd=::GetForegroundWindow();
::PostMessage(hwnd,WM_CLOSE,0,0);
}
}
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}

這時編譯程式,一共出了6個
其中前兩個錯是
error C2065: ‘WH_KEYBOARD_LL’ : undeclared identifier
error C2065: ‘PKBDLLHOOKSTRUCT’ : undeclared identifier
看看msdn 明明說可以有而且在winuser.h中定義了,我試著在KBoardHookDlg.cpp 的前面加入#include “winuser.h” ,但是結果還是一樣的,我們再追根到底再看看winuser.h,發現裡面明明定義了WH_KEYBOARD_LL與PKBDLLHOOKSTRUCT,但是就是不好用,怎麼辦呢?把它們找到,複製到KBoardHookDlg.h中原先手工加入的定義LowLevelKeyboardProc函式上面(下面就是我們要複製的程式碼)

define WH_KEYBOARD_LL 13

typedef struct tagKBDLLHOOKSTRUCT {

DWORD vkCode;

DWORD scanCode;

DWORD flags;

DWORD time;

DWORD dwExtraInfo;

} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
這時再編譯程式,程式就可以運行了。這樣我們就同樣實現了改變鍵盤的目的。
小結,上述兩種方法是不同的原理,其載入方法也不盡相同,對於第一種方法其實現可以在win98/win2000/winXP中都能通用,但對於第兩種方法,其只能在win2000/winXP中應用,如果相在win98中應用就要寫成dll進行呼叫,較為繁瑣,但是其功能是強大的,因為它是與系統進行了掛鉤,所以使用者在任何視窗下按鍵盤都會觸發它,例如我們想遮蔽鍵盤,只要把LowLevelKeyboardProc函式中的return CallNextHookEx(NULL, nCode, wParam, lParam);
改為 return true;就可以遮蔽除Ctrl+Alt+Del以外的所有按鍵。
至於為什麼麼在winuser.h中定義了那兩個量編譯時卻說沒定義,我也是百思不得其解,希望我寫這篇文章能拋磚引玉,激發大家學習vc的興趣。