C# SetWindows钩子停止工作 键盘挂钩未触发事件并在dispose上引发win32异常

C# SetWindows钩子停止工作 键盘挂钩未触发事件并在dispose上引发win32异常,c#,winapi,keyboard,unmanaged,C#,Winapi,Keyboard,Unmanaged,我的c#应用程序创建用于处理键盘事件的键盘挂钩(许多读卡器、扫描仪和其他POS设备模拟键盘)。 有时,我的应用程序创建键盘钩子时不会出错,但不会触发事件并在dispose时抛出异常: System.ComponentModel.Win32Exception(0x80004005):无法删除“应用”的键盘挂钩。错误1404:钩子句柄无效 另一个日志条目是相同的错误,但它说明了 错误\u未\u分配所有\u 我不能在我的电脑上重现这个问题,也不知道我应该调查什么或谷歌。 我知道: 所有具有这种奇怪

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

System.ComponentModel.Win32Exception(0x80004005):无法删除“应用”的键盘挂钩。错误1404:钩子句柄无效

另一个日志条目是相同的错误,但它说明了

错误\u未\u分配所有\u

我不能在我的电脑上重现这个问题,也不知道我应该调查什么或谷歌。 我知道:

  • 所有具有这种奇怪行为的客户端都使用x86操作系统
  • windows特权或权限可能存在一些问题
  • 它有时(不总是)坏
  • 图书馆目标.NET4
  • 应用程序目标.NET 4.5.1
  • 编译平台:任何CPU
另外,我对非托管代码和WinAPI也不太熟悉。我从某个线程中获得了这段代码,并根据我的需要对其进行了修改,但在较高的抽象级别上进行了修改

钩子:

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);

如果你在钩子程序中做错了什么,窗口会在不告诉你的情况下解开钩子

  • 在您的
    底层键盘proc
    中,您没有检查
    nCode
    !必须首先执行此操作,如果
    nCode
    为<0,则必须返回
    CallNextHookEx
    ,而无需任何其他处理
  • 你不能在hook进程中花费太多时间。AFAIK未记录确切限制,并且可以通过注册表值更改。为了安全起见,您应该将目标设定为<200ms

您可以尝试在有问题的系统上设置/更改
低级别hookstimeout
注册表值,看看这是否有帮助。

如果您在hook proc中出错,窗口将在不通知您的情况下解除锁定

  • 在您的
    底层键盘proc
    中,您没有检查
    nCode
    !必须首先执行此操作,如果
    nCode
    为<0,则必须返回
    CallNextHookEx
    ,而无需任何其他处理
  • 你不能在hook进程中花费太多时间。AFAIK未记录确切限制,并且可以通过注册表值更改。为了安全起见,您应该将目标设定为<200ms


您可以尝试在有问题的系统上设置/更改
低级别hookstimeout
注册表值,看看是否有帮助。

非常确定您应该调用
setWindowshookx(WH_-KEYBOARD\LL,_-hookProc,IntPtr.Zero,GetCurrentThreadId())即不传递module@MickyD否,LL钩子仅为全局钩子。如果.NET应用程序在我的系统上安装了全局钩子,它将立即被卸载。LoadLibrary()和FreeLibrary()声明错误,它们缺少SetLastError=true。代码中没有防止意外调用SetWindowsHookEx()两次的保护。这将以垃圾收集器运行时的随机崩溃和1404错误而告终。ERROR_NOT_ALL_ASSIGNED是一个安全错误代码,表示用户帐户没有足够的权限。“这就是我所能看到的一切。”安德斯:是的,当然。感谢您确实应该调用
setWindowshookx(WH_KEYBOARD_LL,_hookProc,IntPtr.Zero,GetCurrentThreadId())即不传递module@MickyD否,LL钩子仅为全局钩子。如果.NET应用程序在我的系统上安装了全局钩子,它将立即被卸载。LoadLibrary()和FreeLibrary()声明错误,它们缺少SetLastError=true。代码中没有防止意外调用SetWindowsHookEx()两次的保护。这将以垃圾收集器运行时的随机崩溃和1404错误而告终。ERROR_NOT_ALL_ASSIGNED是一个安全错误代码,表示用户帐户没有足够的权限。“这就是我所能看到的一切。”安德斯:是的,当然。谢谢你的回答!我会调查的。如果我将添加Thread.Sleep in callback,我可以在我的电脑上复制它吗?据我所知,低级别的钩子刺激只存在于windows 7系统上?所有出现此问题的客户端都运行windows 7。我发现我猜是Windows7和更新版本。所有系统都有一个超时(你不能在超时后阻止/忽略一个键,因为它会忽略你的返回值),但只有7+会解除你的锁定(IIRC)。伙计,你救了我的命。在windows 10的回调中添加Thread.Sleep(2000)后,我在电脑上重现了这种行为!如果您从未真正需要执行/中止击键,那么您可能希望通过对PostMessage的简单调用来实现钩子过程,该调用将消息发布到另一个线程上的某个窗口。谢谢您的回答!我会调查的。如果我将添加Thread.Sleep in callback,我可以在我的电脑上复制它吗?据我所知,低级别的钩子刺激只存在于windows 7系统上?所有出现此问题的客户端都运行windows 7。我发现我猜是Windows7和更新版本。所有系统都有一个超时(你不能在超时后阻止/忽略一个键,因为它会忽略你的返回值),但只有7+会解除你的锁定(IIRC)。伙计,你救了我的命。在windows 10的回调中添加Thread.Sleep(2000)后,我在电脑上重现了这种行为!如果您从未真正需要执行/中止击键,那么您可能希望将钩子过程实现为对PostMessage的简单调用,将消息发布到另一个线程上的某个窗口。