You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:40:36