本節將介紹如何使用Windows API
中的SetWindowsHookEx
和RegisterHotKey
函式來實現鍵盤滑鼠的監控。這些函式可以用來設定全域性鉤子,透過對特定熱鍵掛鉤實現監控的效果,兩者的區別在於SetWindowsHookEx
函式可以對所有執行緒進行監控,包括其他程式中的執行緒,而RegisterHotKey
函式只能對當前執行緒進行監控。
首先我們來實現註冊熱鍵功能,註冊熱鍵可以使用RegisterHotKey()
函式,該函式可以將一個熱鍵與當前應用程式或執行緒繫結,使得當使用者按下熱鍵時,系統會自動將該熱鍵的訊息傳送到該應用程式或執行緒中,該函式原型如下;
BOOL RegisterHotKey(
HWND hWnd,
int id,
UINT fsModifiers,
UINT vk
);
其中,引數的含義如下:
- hWnd:熱鍵所屬的視窗控制程式碼,通常設定為NULL,表示與當前執行緒繫結
- id:熱鍵的ID號,用於區分不同的熱鍵
- fsModifiers:熱鍵的修飾鍵,可以使用組合鍵,例如
Ctrl
、Alt
、Shift
等 - vk:熱鍵的虛擬鍵碼,例如
VK_F1
表示F1
鍵VK_LEFT
表示左箭頭鍵等
函式需要傳入一個視窗控制程式碼、熱鍵ID、熱鍵組合鍵等引數來設定熱鍵。當熱鍵被按下時,系統會自動將一個WM_HOTKEY
訊息傳送給註冊了該熱鍵的視窗,應用程式需要過載該視窗的訊息處理函式來響應該事件,從而實現相應的響應操作。該函式會返回一個BOOL
型別的值,表示熱鍵設定是否成功。
當熱鍵被註冊後則就需要接收熱鍵訊息,通常可以使用GetMessage
函式,該函式用於從訊息佇列中獲取一個訊息並將其儲存在一個結構體中,通常用於在一個迴圈中不斷地獲取訊息,從而實現對Windows
訊息的處理。
該函式的原型定義如下所示;
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
其中,引數的含義如下:
- lpMsg:指向MSG結構體的指標,用於儲存獲取到的訊息
- hWnd:訊息接收者的視窗控制程式碼,通常設定為NULL,表示接收所有視窗的訊息
- wMsgFilterMin:指定獲取訊息的最小訊息值,通常設定為0
- wMsgFilterMax:指定獲取訊息的最大訊息值,通常設定為0
GetMessage函式需要傳入一個指向MSG
結構體的指標,該結構體包含了訊息的各種資訊,例如訊息的型別、傳送者、接收者、時間戳等等。讀者只需要透過判斷函式內的WM_HOTKEY
訊息,並監控是否為我們所需要的即可,如下程式碼是一段註冊熱鍵的實現,分別註冊了Ctrl+F1
, Ctrl+F2
, Ctrl+F3
三個熱鍵組;
#include <windows.h>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
// 分別註冊三個熱鍵 Ctrl+F1 , Ctrl+F2 , Ctrl+F3
if (0 == RegisterHotKey(NULL, 1, MOD_CONTROL, VK_F1))
{
cout << GetLastError() << endl;
}
if (0 == RegisterHotKey(NULL, 2, MOD_CONTROL, VK_F2))
{
cout << GetLastError() << endl;
}
if (0 == RegisterHotKey(NULL, 3, MOD_CONTROL, VK_F3))
{
cout << GetLastError() << endl;
}
// 訊息迴圈
MSG msg = { 0 };
// 從訊息迴圈內讀出按鍵碼
while (GetMessage(&msg, NULL, 0, 0))
{
switch (msg.message)
{
case WM_HOTKEY:
{
if (1 == msg.wParam)
{
std::cout << "CTRL + F1" << std::endl;
}
else if (2 == msg.wParam)
{
std::cout << "CTRL + F2" << std::endl;
}
else if (3 == msg.wParam)
{
std::cout << "CTRL + F3" << std::endl;
}
break;
}
default:
break;
}
}
return 0;
}
讀者可自行編譯上述程式碼片段,並執行,分別按下Ctrl+F1
, Ctrl+F2
, Ctrl+F3
即可看到輸出效果圖;
當然上述方法是區域性的,讀者只能在當前程式內使用,如果離開了程式窗體則這類熱鍵將會失效,此時我們就需要使用SetWindowsHookEx
函式註冊全域性鉤子,該函式可以在系統中安裝鉤子,以便監視或攔截特定的事件或訊息。
以下是SetWindowsHookEx的函式原型:
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
引數說明:
- idHook:鉤子型別,可以是WH_KEYBOARD(鍵盤鉤子)或WH_MOUSE(滑鼠鉤子)等
- lpfn:回撥函式,當特定事件或訊息發生時,作業系統會呼叫此函式。該函式的返回值由鉤子型別和引數決定
- hMod:包含
lpfn
的DLL
控制程式碼。如果lpfn
引數在當前程式內,則該引數可以為NULL - dwThreadId:執行緒識別符號,指定與鉤子相關聯的執行緒。如果
dwThreadId
引數為0,則鉤子將應用於所有執行緒
函式會返回一個型別為HHOOK
的控制程式碼,該控制程式碼可以在解除安裝鉤子時使用,讀者需要注意由於全域性鉤子會影響系統效能,因此在使用SetWindowsHookEx
函式時應謹慎,並在使用結束後及時的透過UnhookWindowsHookEx
釋放鉤子控制程式碼。
如下所示程式碼則是一個鍵盤鉤子監控案例,在該案例中我們透過SetWindowsHookEx
註冊一個全域性鉤子,並設定回撥函式LowLevelKeyboardProc
透過使用PeekMessageA
監控鍵盤事件,當有鍵盤事件產生時則自動路由到LowLevelKeyboardProc
函式內,此時即可得到按鍵的型別以及按下鍵位,如下所示;
#include <windows.h>
#include <iostream>
#include <conio.h>
using namespace std;
// 鉤子控制程式碼
HHOOK keyboardHook = 0;
// 鍵盤鉤子
LRESULT CALLBACK LowLevelKeyboardProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;
if (ks->flags == 128 || ks->flags == 129)
{
// 監控按鍵狀態
if (nCode >= 0)
{
switch (wParam)
{
case WM_KEYDOWN:
cout << "普通按鍵抬起" << endl;
break;
case WM_KEYUP:
cout << "普通按鍵按下" << endl;
break;
case WM_SYSKEYDOWN:
cout << "系統按鍵抬起" << endl;
break;
case WM_SYSKEYUP:
cout << "系統按鍵按下" << endl;
break;
}
}
// 監控鍵盤,並判斷鍵
switch (ks->vkCode)
{
case 0x41:
cout << "檢測到按鍵:" << "A" << endl;
break;
case 0x0D:
cout << "檢測到按鍵:" << "Enter" << endl;
break;
case 0xA0: case 0xA1:
cout << "檢測到按鍵:" << "Shift" << endl;
break;
case 0x08:
cout << "檢測到按鍵:" << "Backspace" << endl;
break;
case 0x20:
cout << "檢測到按鍵:" << "Space" << endl;
break;
}
// 直接返回1可以使按鍵失效
//return 1;
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
int main(int argc, char* argv[])
{
// 安裝鉤子WH_KEYBOARD_LL為鍵盤鉤子
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleA(NULL), NULL);
if (keyboardHook == 0)
{
cout << "掛鉤鍵盤失敗" << endl; return -1;
}
MSG msg;
while (1)
{
if (PeekMessageA(&msg, NULL, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else
Sleep(0);
}
UnhookWindowsHookEx(keyboardHook);
return 0;
}
編譯並執行上述程式碼,讀者可自行按下鍵盤鍵位,則可看到如下圖所示的輸出;
滑鼠鉤子的掛鉤與鍵盤基本一致,只是在呼叫SetWindowsHookEx
傳遞引數時設定了WH_MOUSE_LL
滑鼠事件,當有滑鼠訊息時則透過MouseProc
滑鼠回撥函式執行,
#include <windows.h>
#include <iostream>
#include <conio.h>
using namespace std;
// 鉤子控制程式碼
HHOOK keyboardHook = 0;
// 滑鼠鉤子
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
LPMSLLHOOKSTRUCT p = (LPMSLLHOOKSTRUCT)lParam;
POINT pt = p->pt;
DWORD mouseData = p->time;
const char* info = NULL;
char text[60], pData[50], mData[50];
PAINTSTRUCT ps;
HDC hdc;
if (nCode >= 0)
{
if (wParam == WM_MOUSEMOVE)
{
info = "滑鼠 [移動]";
}
else if (wParam == WM_LBUTTONDOWN)
{
info = "滑鼠 [左鍵] 按下";
}
else if (wParam == WM_LBUTTONUP)
{
info = "滑鼠 [左鍵] 抬起";
}
else if (wParam == WM_LBUTTONDBLCLK)
{
info = "滑鼠 [左鍵] 雙擊";
}
else if (wParam == WM_RBUTTONDOWN)
{
info = "滑鼠 [右鍵] 按下";
}
else if (wParam == WM_RBUTTONUP)
{
info = "滑鼠 [右鍵] 抬起";
}
else if (wParam == WM_RBUTTONDBLCLK)
{
info = "滑鼠 [右鍵] 雙擊";
}
else if (wParam == WM_MBUTTONDOWN)
{
info = "滑鼠 [滾輪] 按下";
}
else if (wParam == WM_MBUTTONUP)
{
info = "滑鼠 [滾輪] 抬起";
}
else if (wParam == WM_MBUTTONDBLCLK)
{
info = "滑鼠 [滾輪] 雙擊";
}
else if (wParam == WM_MOUSEWHEEL)
{
info = "滑鼠 [滾輪] 滾動";
}
ZeroMemory(text, sizeof(text));
ZeroMemory(pData, sizeof(pData));
ZeroMemory(mData, sizeof(mData));
std::cout << "滑鼠狀態: " << info << std::endl;
std::cout << "X: " << pt.x << " Y: " << pt.y << std::endl;
std::cout << "附加資料: " << mouseData << std::endl;
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
int main(int argc, char* argv[])
{
// 安裝鉤子
keyboardHook = SetWindowsHookEx(WH_MOUSE_LL,MouseProc,GetModuleHandleA(NULL),NULL);
if (keyboardHook == 0)
{
cout << "掛鉤滑鼠失敗" << endl; return -1;
}
MSG msg;
while (1)
{
// 如果訊息佇列中有訊息
if (PeekMessageA(&msg,NULL,NULL,NULL,PM_REMOVE))
{
// 把按鍵訊息傳遞給字元訊息
TranslateMessage(&msg);
// 將訊息分派給視窗程式
DispatchMessageW(&msg);
}
else
Sleep(0);
}
UnhookWindowsHookEx(keyboardHook);
return 0;
}
讀者可自行編譯並執行上述程式碼片段,當掛鉤後我們就可以看到滑鼠的移動位置以及滑鼠擊鍵情況,如下圖所示;
本文作者: 王瑞
本文連結: https://www.lyshark.com/post/bdb59f93.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協議。轉載請註明出處!