重定義鍵盤的兩種實現方法
聯系方式:大連理工大學電子系995班孫宇哲
Email:sunyuzhe@263.net
主頁:http://sunyuzhe.363.net
Tel:0411-4702214
在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的興趣。
注(作者同意作品進刊盤和網絡版)
聯系方式:大連理工大學電子系995班孫宇哲
Email:sunyuzhe@263.net
主頁:http://sunyuzhe.363.net
Tel:0411-4702214