.NET 8 WPF中SetWindowsHookEx返回0且错误码87的问题
问题分析与解决方案
错误码87(ERROR_INVALID_PARAMETER)是因为你的SetWindowsHookEx调用中有多个参数问题,逐一修正如下:
核心错误点
- 返回值类型错误:
SetWindowsHookEx的返回值是HHOOK(钩子句柄),属于指针类型,应该用IntPtr而非int,否则在64位系统会出现句柄截断,直接触发参数无效判定。 - hInstance参数错误:注册线程钩子时,必须传入包含钩子过程的模块句柄,不能传
null。系统需要通过这个句柄定位钩子函数的代码位置。 - threadId参数错误:
Environment.CurrentManagedThreadId是.NET托管线程ID,和Windows原生线程ID不是同一概念,必须使用原生线程ID才能正确绑定钩子到目标线程。 - 委托生命周期问题:如果钩子委托是局部变量,会被GC回收,导致钩子失效(虽然这不是当前错误码的直接原因,但后续会引发其他问题)。
修正后的完整代码
1. 正确导入Win32 API与钩子实现
using System; using System.Runtime.InteropServices; public class MouseHookHelper { // 修正返回值类型为IntPtr,兼容32/64位系统 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, uint threadId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern int CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern bool UnhookWindowsHookEx(IntPtr hHook); // 获取当前原生Windows线程ID [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); // 获取当前模块句柄(传入null表示当前进程主模块) [DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandle(string lpModuleName); public const int WH_MOUSE = 7; private delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam); // 保持委托强引用,防止被GC回收 private static HookProc _hookProcInstance; private static IntPtr _mouseHookHandle = IntPtr.Zero; // 注册鼠标钩子 public static bool RegisterMouseHook() { // 初始化委托实例,确保强引用 _hookProcInstance = MouseHookProc; // 获取当前模块句柄 IntPtr moduleHandle = GetModuleHandle(null); if (moduleHandle == IntPtr.Zero) { return false; } // 获取当前原生线程ID uint threadId = GetCurrentThreadId(); // 注册钩子 _mouseHookHandle = SetWindowsHookEx(WH_MOUSE, _hookProcInstance, moduleHandle, threadId); return _mouseHookHandle != IntPtr.Zero; } // 卸载钩子 public static void UnregisterMouseHook() { if (_mouseHookHandle != IntPtr.Zero) { UnhookWindowsHookEx(_mouseHookHandle); _mouseHookHandle = IntPtr.Zero; _hookProcInstance = null; } } // 钩子处理函数 private static int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) { // nCode < 0时必须直接调用下一个钩子并返回结果 if (nCode >= 0) { // 在这里处理你的鼠标事件逻辑 // wParam对应鼠标消息类型,比如WM_LBUTTONDOWN、WM_MOUSEMOVE等 // lParam是指向MOUSEHOOKSTRUCT的指针,可以解析获取鼠标位置等信息 // 示例:解析鼠标位置 // MOUSEHOOKSTRUCT hookStruct = Marshal.PtrToStructure<MOUSEHOOKSTRUCT>(lParam); // Console.WriteLine($"鼠标位置:{hookStruct.pt.X}, {hookStruct.pt.Y}"); } // 必须调用下一个钩子,否则其他钩子链会断裂 return CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam); } // 定义MOUSEHOOKSTRUCT结构体用于解析lParam [StructLayout(LayoutKind.Sequential)] private struct MOUSEHOOKSTRUCT { public POINT pt; public IntPtr hwnd; public uint wHitTestCode; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] private struct POINT { public int X; public int Y; } }
2. 使用示例
// 注册钩子 bool hookRegistered = MouseHookHelper.RegisterMouseHook(); if (!hookRegistered) { int errorCode = Marshal.GetLastWin32Error(); Console.WriteLine($"注册失败,错误码:{errorCode}"); } // 程序退出时卸载钩子 // MouseHookHelper.UnregisterMouseHook();
额外说明
- 如果需要全局鼠标钩子(监听所有线程的鼠标事件),则需要将钩子过程放在单独的DLL中,同时
threadId传0,hInstance传入DLL的模块句柄(全局钩子的实现复杂度更高,需要处理跨进程调用和DLL注入的问题)。 - 钩子不再使用时必须调用
UnhookWindowsHookEx卸载,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Holunk
相关产品推荐
相关产品推荐

