【问题标题】:SetWindowsHookEx hook stops workingSetWindowsHookEx 挂钩停止工作
【发布时间】:2018-07-19 15:03:00
【问题描述】:

键盘挂钩未触发事件并在 dispose 时引发 win32 异常

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

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

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

ERROR_NOT_ALL_ASSIGNED

Source code of library and demo app.

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

  • 所有出现这种奇怪行为的客户端都使用 x86 操作系统。
  • Windows 特权或权限可能存在一些问题。
  • 有时会中断(并非总是如此)。
  • 库面向 .NET 4
  • 应用程序面向 .NET 4.5.1
  • 编译平台:任何 CPU

另外,我不熟悉非托管代码和 win 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);

【问题讨论】:

  • 很确定您应该调用SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, IntPtr.Zero, GetCurrentThreadId ());,即不要为 module 传递值
  • @MickyD 不,LL 挂钩是全局的。
  • 如果 .NET 应用程序在我的系统上安装了全局挂钩,它将立即被卸载。
  • LoadLibrary() 和 FreeLibrary() 声明错误,它们缺少 SetLastError = true。代码中没有防止意外调用 SetWindowsHookEx() 两次的保护措施。当垃圾收集器运行和 1404 错误时,这将导致随机崩溃。 ERROR_NOT_ALL_ASSIGNED 是一个安全错误代码,表示用户帐户没有足够的权限。我能看到的就这些了。
  • @Anders 当然是的。谢谢

标签: c# winapi keyboard unmanaged


【解决方案1】:

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

  • 在您的LowLevelKeyboardProc 中,您没有检查nCode!您必须先执行此操作,如果 nCode 为 必须返回 CallNextHookEx 而不进行任何其他处理。
  • 您不能在钩子进程中花费太多时间。 AFAIK 没有记录确切的限制,可以通过注册表值更改。您应该以

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

【讨论】:

  • 感谢您的回答!我会调查的。如果我将在回调中添加 Thread.Sleep,我可以在我的 PC 上重现它吗?
  • 据我了解 LowLevelHooksTimeout 仅存在于 Windows 7 系统上?所有有此问题的客户端都在运行 Windows 7。我找到了 this article
  • 我猜它是 Windows 7 和更新版本。所有系统都有超时(您不能在超时后阻止/忽略某个键,因为它会忽略您的返回值)但只有 7+ 会解除您的钩子(IIRC)。
  • 伙计,你救了我的命。在我的 Windows 10 回调中添加 Thread.Sleep(2000) 后,我在我的电脑上重现了这种行为!
  • 如果您实际上从不需要吃/中止击键,您可能希望将 hook proc 实现为对 PostMessage 的简单调用,该调用将消息发布到另一个线程上的一个窗口。
猜你喜欢
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 1970-01-01
  • 2010-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
相关资源
最近更新 更多