在 C# 中實作系統層級的鍵盤與滑鼠鉤子,一直是開發者面臨的挑戰。本文完整展示如何封裝 Windows API 實現全域攔截、監控與屏蔽功能,並解決委托被垃圾回收導致的 CallbackOnCollectedDelegate 問題。
三大關鍵步驟:
SetWindowsHookEx(WH_KEYBOARD_LL / WH_MOUSE_LL, ...)CallNextHookEx 傳遞訊息給下一個鉤子UnhookWindowsHookEx 釋放資源封裝類別 Hocy_Hook 核心設計要點:
private HookProc m_KeyboardHookProcedure;
private HookProc m_MouseHookProcedure;
// 切勿省略此欄位,否則委托會被 GC 回收觸發 MDA 錯誤
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) {
switch (flags) {
case "2": return 1; // 屏蔽模式:直接攔截不傳遞
case "1": break; // 監控模式:繼續處理事件
}
bool handled = false;
if (nCode >= 0 && (OnKeyDown != null || OnKeyUp != null || OnKeyPress != null)) {
KeyboardHookStruct khs = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
// KeyDown 事件
if (OnKeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) {
Keys key = (Keys)khs.VKCode;
KeyEventArgs e = new KeyEventArgs(key);
OnKeyDown(this, e);
handled |= e.Handled;
}
// KeyPress 事件(含大小寫判斷)
if (OnKeyPress != null && wParam == WM_KEYDOWN) {
// 取得 Shift、CapsLock 狀態轉換字元...
OnKeyPress(this, e);
handled |= e.Handled;
}
// KeyUp 事件
if (OnKeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)) {
OnKeyUp(this, new KeyEventArgs((Keys)khs.VKCode));
}
}
return handled ? 1 : CallNextHookEx(m_pKeyboardHook, nCode, wParam, lParam);
}
public bool InstallHook(string flagsinfo) {
this.flags = flagsinfo;
IntPtr hInstance = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().ManifestModule);
if (m_pMouseHook == IntPtr.Zero) {
m_MouseHookProcedure = new HookProc(MouseHookProc);
m_pMouseHook = SetWindowsHookEx(WH_MOUSE_LL, m_MouseHookProcedure, hInstance, 0);
if (m_pMouseHook == IntPtr.Zero) { UnInstallHook(); return false; }
}
if (m_pKeyboardHook == IntPtr.Zero) {
m_KeyboardHookProcedure = new HookProc(KeyboardHookProc);
m_pKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, m_KeyboardHookProcedure, hInstance, 0);
if (m_pKeyboardHook == IntPtr.Zero) { UnInstallHook(); return false; }
}
return true;
}
public bool UnInstallHook() {
bool result = true;
if (m_pMouseHook != IntPtr.Zero) {
result &= UnhookWindowsHookEx(m_pMouseHook);
m_pMouseHook = IntPtr.Zero;
}
if (m_pKeyboardHook != IntPtr.Zero) {
result &= UnhookWindowsHookEx(m_pKeyboardHook);
m_pKeyboardHook = IntPtr.Zero;
}
return result;
}
private Hocy_Hook hook_Main = new Hocy_Hook();
private void btnStartMonitor_Click(object sender, EventArgs e) {
hook_Main.InstallHook("1"); // 監控模式
}
private void btnBlockKeyboard_Click(object sender, EventArgs e) {
hook_Main.InstallHook("2"); // 屏蔽鍵盤
}
private void btnStop_Click(object sender, EventArgs e) {
hook_Main.UnInstallHook();
}
GetHINSTANCE 取得的值必須為 4194304 (0x400000),否則安裝失敗WH_KEYBOARD_LL 為全域低階鉤子,不需 DLL 注入IntPtr 自動適配
程式執行介面示意圖