SetWindowsHookEx 挂钩停止工作

SetWindowsHookEx hook stops working

键盘挂钩未触发事件并在处理时抛出 win32 异常

我的 c# 应用程序创建键盘挂钩来处理键盘事件(许多读卡器、扫描仪和其他 POS 设备模拟键盘)。 有时我的应用程序创建键盘钩子没有错误,但它没有触发事件并且在处理时抛出异常:

System.ComponentModel.Win32Exception (0x80004005): Failed to remove keyboard hooks for 'app'. Error 1404: Invalid hook handle

其他日志条目是同样的错误,但它告诉

ERROR_NOT_ALL_ASSIGNED

Source code of library and demo app.

我无法在我的电脑上重现这个问题,不知道我应该调查什么或 google。 我知道:

此外,我对非托管代码并不流利,所以我赢了 api。我从某个线程获得了这段代码,并根据我的需要对其进行了修改,但抽象程度较高。

钩子函数:

public GlobalKeyboardHook()
{
    _windowsHookHandle = IntPtr.Zero;
    _user32LibraryHandle = IntPtr.Zero;
    _hookProc = LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.

    _user32LibraryHandle = LoadLibrary("User32");
    if (_user32LibraryHandle == IntPtr.Zero)
    {
        int errorCode = Marshal.GetLastWin32Error();
        throw new Win32Exception(errorCode,
            $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
    }


    _windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
    if (_windowsHookHandle == IntPtr.Zero)
    {
        int errorCode = Marshal.GetLastWin32Error();
        throw new Win32Exception(errorCode,
            $"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
    }
}
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("USER32", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);

挂钩处理:

protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        // because we can unhook only in the same thread, not in garbage collector thread
        if (_windowsHookHandle != IntPtr.Zero)
        {
            if (!UnhookWindowsHookEx(_windowsHookHandle))
            {
                int errorCode = Marshal.GetLastWin32Error();
                throw new Win32Exception(errorCode,
                    $"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
            }
            _windowsHookHandle = IntPtr.Zero;

            // ReSharper disable once DelegateSubtraction
            _hookProc -= LowLevelKeyboardProc;
        }
    }

    if (_user32LibraryHandle != IntPtr.Zero)
    {
        if (!FreeLibrary(_user32LibraryHandle)) // reduces reference to library by 1.
        {
            int errorCode = Marshal.GetLastWin32Error();
            throw new Win32Exception(errorCode,
                $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
        }
        _user32LibraryHandle = IntPtr.Zero;
    }
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool FreeLibrary(IntPtr hModule);

[DllImport("USER32", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hHook);

如果你在 hook 过程中做错了什么 Windows 会在不告诉你的情况下解开你。

  • 在您的 LowLevelKeyboardProc 中您没有检查 nCode!你必须先这样做,如果 nCode < 0 你 必须 return 和 CallNextHookEx 没有任何其他处理。
  • 你不能在钩子过程中花太多时间。确切的限制没有记录 AFAIK 可以通过注册表值更改。为了安全起见,您的目标应该是 < 200 毫秒。

您可以在有问题的系统上尝试 setting/changing LowLevelHooksTimeout 注册表值,看看是否有帮助。