Windows Hook(鉤子)是一種強大的機制,允許應用程式攔截並處理系統訊息,如鍵盤、滑鼠事件。透過 SetWindowsHookEx API,我們可以將自訂的 Hook 程序掛入鉤子鏈,實現全域或執行緒專用的事件監控。
本文以滑鼠 Hook(WH_MOUSE)為例,示範如何在 Visual C# .NET 中建立特定執行緒的 Hook,並在表單標題列即時顯示滑鼠座標。
所有 Win32 API 呼叫皆需透過 System.Runtime.InteropServices 進行平台呼叫(P/Invoke)。以下為必要的委派、結構與外部方法宣告:
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
public const int WH_MOUSE = 7; // 滑鼠 Hook 類型
static int hHook = 0; // Hook 控制碼
HookProc MouseHookProcedure;
[StructLayout(LayoutKind.Sequential)]
public class POINT { public int x; public int y; }
[StructLayout(LayoutKind.Sequential)]
public class MouseHookStruct
{
public POINT pt;
public int hwnd;
public int wHitTestCode;
public int dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
ThreadSpecificMouseHookForm1using System.Runtime.InteropServices;
private void button1_Click(object sender, EventArgs e)
{
if (hHook == 0) // 安裝 Hook
{
MouseHookProcedure = new HookProc(Form1.MouseHookProc);
hHook = SetWindowsHookEx(WH_MOUSE, MouseHookProcedure, (IntPtr)0, AppDomain.GetCurrentThreadId());
if (hHook == 0)
{
MessageBox.Show("SetWindowsHookEx Failed");
return;
}
button1.Text = "UnHook Windows Hook";
}
else // 移除 Hook
{
bool ret = UnhookWindowsHookEx(hHook);
if (!ret) MessageBox.Show("UnhookWindowsHookEx Failed");
hHook = 0;
button1.Text = "Set Windows Hook";
this.Text = "Mouse Hook";
}
}
關鍵點:threadId 使用 AppDomain.GetCurrentThreadId() 限定為當前執行緒,實現「執行緒專用 Hook」。
public static int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0) return CallNextHookEx(hHook, nCode, wParam, lParam);
// 將非受控記憶體封送為 Managed 結構
MouseHookStruct info = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
string caption = $"x = {info.pt.x} y = {info.pt.y}";
Form.ActiveForm.Text = caption; // 更新標題列
return CallNextHookEx(hHook, nCode, wParam, lParam); // 傳遞給下一個 Hook
}
無法在 .NET Framework 中實作全域 Hook。
全域 Hook 需要原生 DLL 匯出函式以注入其他處理序,而 Managed 程式碼的函式指標是動態建立的 Proxy,無法提供穩定的匯出位址。因此,若需全域鍵盤/滑鼠監控,必須撰寫 C++ DLL 並透過 P/Invoke 呼叫。
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace ThreadSpecificMouseHook
{
public class Form1 : Form
{
// ... 委派、常數、結構、DllImport 宣告 ...
private Button button1;
public Form1()
{
button1 = new Button { Text = "Set Windows Hook", Dock = DockStyle.Top };
button1.Click += button1_Click;
Controls.Add(button1);
}
// ... button1_Click, MouseHookProc ...
}
}
透過 SetWindowsHookEx 與 P/Invoke,C# 可輕鬆實作執行緒專用 Hook,監控特定執行緒的滑鼠、鍵盤事件。雖然 .NET 原生不支援全域 Hook,但針對單一應用程式內的事件攔截,此方案已足夠強大且易於維護。