Windows的消息沟子
using System.Windows.Interop; public partial class MainWindow : Window { private const int WM_LBUTTONDOWN = 0x0201; private IntPtr _hookID = IntPtr.Zero; // 钩子回调函数 private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); // 创建一个成员委托,将其用作成员以防止被垃圾收集 private HookProc _proc; // 注册指定窗口的鼠标钩子 private void RegisterMouseHook() { var hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle); hwndSource.AddHook(WndProc); _proc = HookCallback; _hookID = SetWindowsHookEx(WH_MOUSE_LL, _proc, IntPtr.Zero, GetCurrentThreadId()); } // 取消注册指定窗口的鼠标钩子 private void UnregisterMouseHook() { if (_hookID != IntPtr.Zero) { UnhookWindowsHookEx(_hookID); _proc = null; var hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle); hwndSource.RemoveHook(WndProc); } } // 窗口消息处理函数 private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { case WM_DESTROY: UnregisterMouseHook(); break; } return IntPtr.Zero; } // 钩子回调函数 private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && wParam == (IntPtr)WM_LBUTTONDOWN) { // 处理鼠标左键按下事件 } return CallNextHookEx(_hookID, nCode, wParam, lParam); } } // 声明 Windows API 函数和常量 private const int WH_MOUSE_LL = 14; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern uint GetCurrentThreadId();
注册沟子简单,核心容易忘的一点就是全局沟子还是单窗口沟子
安装钩子和卸载钩子关键就是SetWindowsHookEx和UnhookWindowsHookEx方法。
SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId) 函数将钩子加入到钩子链表中,说明一下四个参数:
idHook 钩子类型,即确定钩子监听哪种消息, 可以监视窗口过程,也监视消息队列。上面的代码中设为2,即监听键盘消息并且是线程钩子,如果是全局钩子监听键盘消息应设为13,线程钩子监听鼠标消息设为7,全局钩子监听鼠标消息设为14。
代码为5,即C++中的WH_CBT (WH_CBT 当Windows激活、产生、释放(关闭)、最小化、最大化或改变窗口时都将触发此事件)
lpfn 钩子回调的地址指针。根据钩子类型,设置不同的回调函数。如果threadId参数为0 或是一个由别的进程创建的线程的标识,lpfn必须指向DLL中的钩子子程。 除此以外,lpfn可以指向当前进程的一段钩子回调代码。钩子函数的入口地址,当钩子钩到任何消息后立刻调用这个函数。
hInstance 应用程序(dll)实例的句柄。标识包含lpfn所指的回调的DLL。如果threadId 表示当前进程创建的一个线程,而且子程代码位于当前进程,hInstance必须为NULL(即线程钩子传null)。
threadId 设置钩子的线程ID,如果为0 则设置为全局钩子
上面代码中的SetWindowsHookEx方法安装的是线程钩子,用GetCurrentThreadId()函数得到当前的线程ID,钩子就只监听当前线程的键盘消息。
UnhookWindowsHookEx (int idHook) 函数用来卸载钩子,卸载钩子与加入钩子链表的顺序无关,并非后进先出
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南