前陣子曾撰寫《利用底層鍵盤鉤子屏蔽任意按鍵》並發表於部落格。本文標題將「屏蔽」改為「攔截」,功能更強大。舊版 DLL 僅能實現屏蔽,若想在屏蔽前加入「小動作」,必須修改 DLL 在 LowLevelKeyboardProc 中添加程式碼,缺乏彈性與通用性。因此引入回調機制:Windows 許多 API 需回調函式,我們也可撰寫此類 API,給 DLL 呼叫程式保留充足介面。DLL 如同閥門,不關心的按鍵訊息放行,只攔截關心的按鍵訊息進一步處理,處理程式碼寫在 DLL 呼叫程式的回調函式中,最理想不過。
/********************************************************************/
/* 檔案名: MaskKey.cpp */
/* 功能: 標準 DLL ---- 利用底層鍵盤鉤子實現攔截鍵盤任意按鍵 */
/* 作者: 盧培培 (goodname008) */
MaskKey.h 標頭檔中需加入:
// 回調函式指標
typedef BOOL (CALLBACK* LPFNKEYBOARDPROC)(WPARAM, KBDLLHOOKSTRUCT*);
兩個 Dialog 成員函式(對應兩個按鈕)加上一個回調函式:
// 全域鍵盤鉤子回調函式
// 參數: action 標識鍵盤訊息(按下,彈起), keyStruct 包含按鍵資訊
BOOL CALLBACK KeyboardProc(WPARAM action, KBDLLHOOKSTRUCT* pKeyStruct)
{
// 判斷按鍵動作
switch (action)
{
case WM_KEYDOWN: break;
case WM_KEYUP: break;
case WM_SYSKEYDOWN: break;
case WM_SYSKEYUP: break;
}
// 回傳 true 表示繼續傳遞按鍵訊息
// 回傳 false 表示結束按鍵訊息傳遞
return false;
}
void CMaskKeyAppDlg::OnStartmaskkey()
{
// 屏蔽 A, B, C, 上, 下, 左, 右及兩個 Win 鍵
DWORD dwVK[] = {'A', 'B', 'C', VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_LWIN, VK_RWIN};
int nLength = sizeof(dwVK) / sizeof(DWORD);
StartMaskKey(dwVK, nLength, KeyboardProc);
}
void CMaskKeyAppDlg::OnStopmaskkey()
{
StopMaskKey();
}
StartMaskKey 新增函式指標參數,即回調函式。DLL 中 StartMaskKey 收到函式指標後儲存於 g_lpfnKeyboardProc 變數,然後在 LowLevelKeyboardProc 發現要攔截的按鍵時,透過函式指標呼叫回調函式,將控制權完全交還給 DLL 呼叫程式。action 參數標識鍵盤訊息,pKeyStruct 包含豐富按鍵資訊(實際為系統傳給 LowLevelKeyboardProc 的 lParam)。最重要的是回調函式回傳值:如閥門開關決定按鍵訊息命運。若回傳 true 則繼續傳遞給系統下一個鉤子;若 false 則結束傳遞,達到攔截效果。
在視窗表單加入 2 個 CommandButton,分別改名 cmdStartMask、cmdStopMask:
Option Explicit
Private Declare Function StartMaskKey Lib "MaskKey" (lpdwVirtualKey As Long, ByVal nLength As Long, ByVal lpfnKeyboarProc As Long, Optional ByVal bDisableKeyboard As Boolean = False) As Long
Private Declare Function StopMaskKey Lib "MaskKey" () As Long
Private Sub cmdStartMask_Click()
' 屏蔽 A, B, C, 上, 下, 左, 右及兩個 Win 鍵
Dim key(8) As Long
key(0) = vbKeyA
key(1) = vbKeyB
key(2) = vbKeyC
key(3) = vbKeyLeft
key(4) = vbKeyRight
key(5) = vbKeyUp
key(6) = vbKeyDown
key(7) = &H5B ' 左邊 Win 鍵
key(8) = &H5C ' 右邊 Win 鍵
StartMaskKey key(0), UBound(key) + 1, AddressOf KeyboardProc
End Sub
Private Sub cmdStopMask_Click()
StopMaskKey
End Sub
VB 標準模組程式碼(回調函式 必須 寫在標準模組中):
Option Explicit
Private Const WM_KEYDOWN = &H100
Private Const WM_KEYUP = &H101
Private Const WM_SYSKEYDOWN = &H104
Private Const WM_SYSKEYUP = &H105
Public Type KBDLLHOOKSTRUCT
vkCode As Long ' 虛擬按鍵碼 (1-254)
scanCode As Long ' 硬體按鍵掃描碼
flags As Long ' flags
time As Long ' 訊息時間戳
dwExtraInfo As Long ' 額外資訊
End Type
Public Enum KEYACTION
ACTION_KEYDOWN = WM_KEYDOWN
ACTION_KEYUP = WM_KEYUP
ACTION_SYSKEYDOWN = WM_SYSKEYDOWN
ACTION_SYSKEYUP = WM_SYSKEYUP
End Enum
' 全域鍵盤鉤子回調函式
' 參數: action 標識鍵盤訊息(按下,彈起), keyStruct 包含按鍵資訊
Public Function KeyboardProc(ByVal action As KEYACTION, keyStruct As KBDLLHOOKSTRUCT) As Boolean
Select Case action
Case ACTION_KEYDOWN
Debug.Print keyStruct.vkCode, "按下鍵盤按鍵"
Case ACTION_KEYUP
Debug.Print keyStruct.vkCode, "彈起鍵盤按鍵"
Case ACTION_SYSKEYDOWN
Case ACTION_SYSKEYUP
End Select
' 回傳 True 表示繼續傳遞按鍵訊息
' 回傳 False 表示結束按鍵訊息傳遞
KeyboardProc = False
End Function
與 VC 版呼叫範例類似,僅語法轉換為 VB。對 action 參數改用列舉型別以利理解。VB 回調函式必須寫在標準模組中。
關於全域鍵盤鉤子內容告一段落。利用 VC 撰寫的 DLL,VB 也能方便實現全域鍵盤鉤子。當然不僅限於鍵盤鉤子,此方法可實現任何類型鉤子。