什么会导致 Windows 取消挂钩低级别(全局)键盘挂钩?

Posted

技术标签:

【中文标题】什么会导致 Windows 取消挂钩低级别(全局)键盘挂钩?【英文标题】:What can cause Windows to unhook a low level (global) keyboard hook? 【发布时间】:2010-04-16 18:22:16 【问题描述】:

我们通过SetWindowsHookExWH_KEYBOARD_LL 安装了一些全局键盘挂钩,这些挂钩似乎会被Windows 随机解除挂钩。

我们验证了它们的钩子不再附加,因为在句柄上调用 UnhookWindowsHookEx 会返回 false。 (还验证了它在正常工作时返回true

似乎没有一致的重现,我听说它们可能会由于超时或抛出异常而脱钩,但我已经尝试过两种方法都只是让它在处理方法中的断点上停留一段时间一分钟,以及只是抛出一个随机异常(C#),它似乎仍然有效。

在我们的回调中,我们快速发布到另一个线程,所以这可能不是问题。我已经阅读了 Windows 7 中有关在注册表中设置更高超时的解决方案,因为 Windows 7 显然对超时更具侵略性(我们都在这里运行 Win7,所以不确定这是否发生在其他操作系统上),但这并没有这似乎不是一个理想的解决方案。

我考虑过只运行一个后台线程以每隔一段时间刷新一次钩子,这很骇人听闻,但我不知道这样做有什么真正的负面后果,而且这似乎比更改全局更好Windows 注册表设置。

还有其他建议或解决方案吗? 设置钩子的类和它们所附加的委托都是静态的,因此它们不应该被 GC 处理。

编辑:通过调用GC.Collect(); 验证它们仍然有效,因此它们不会被垃圾收集。

【问题讨论】:

让 SetWindowsHookEx 调用退出或终止的线程会执行此操作。 @Hans 这不是给我们的,但它可以帮助其他人。他们在Application.Run 之前被连接起来,之后就被解开。 我似乎记得托管线程与非托管线程不是 1:1 的。也许非托管线程终止了? 就超时而言,您是否在钩子回调中尝试过System.Threading.Thread.Sleep()?可能是一个很长的镜头,但使用 IDE 断点可能不能准确表示阻塞的钩子回调。 @Zach 尝试在回调中添加System.Threading.Thread.Sleep(10000);,但它仍然有效。我们绝对不会阻塞超过 10 秒,应该少于 1/4 秒。 【参考方案1】:

我认为这一定是超时问题。

其他开发人员报告了一个特定于 Windows7 的问题,即如果低级挂钩超过(未记录的)超时值,则会被解除挂钩。

请参阅this thread 了解其他讨论相同问题的开发人员。可能是您需要执行繁忙的循环(或缓慢的垃圾收集)而不是睡眠来导致脱钩行为。 LowLevelKeyboardProc 函数中的断点也可能会产生超时问题。 (还有一个观察结果是,另一个任务的 CPU 负载过重可能会引发这种行为 - 可能是因为另一个任务从 LowLevelKeyboardProc 函数中窃取了 CPU 周期并导致它花费了太长时间。)

该线程中建议的解决方案是尝试将 HKEY_CURRENT_USER\Control Panel\Desktop 注册表中的 LowLevelHooksTimeout DWORD 值设置为更大的值。

请记住,C# 的一大优点是,如果发生垃圾回收,即使是简单的语句也会花费过多的时间。这(或其他线程的 CPU 负载)可能解释了问题的间歇性。

【讨论】:

您不必去 MSDN 论坛上寻找来确认这一点;它有很好的记录here in the SDK docs。关注标题为“备注”的部分。【参考方案2】:

我想到了两件事可能会帮助您找出问题所在。

    为了帮助隔离问题的位置,在当前挂钩的同时运行另一个WH_KEYBOARD_LL 挂钩,让它除了将数据传递到挂钩链上之外什么都不做。当你发现你原来的钩子没有上钩时,检查一下这个“虚拟”钩子是否也没有上钩。如果“虚拟”钩子也没有被钩住,你可以相当肯定问题不在你的钩子上(即在 Windows 中或与你的整个进程相关的东西?)如果“虚拟”钩子没有被钩住,那么问题就出在可能在你的钩子里。

    记录通过回调传递给您的钩子的信息并运行它,直到钩子被取消钩子。重复此操作多次并检查记录的数据,看看您是否可以识别导致脱钩的模式。

我会一次尝试这些,以防万一会影响其他人的结果。如果在此之后您对问题可能是什么没有任何线索,您可以尝试将它们一起运行。

【讨论】:

我们实际上有 3 个 WH_KEYBOARD_LL 钩子,每个钩子都以 CallNextHookEx 结尾,当它停止工作时,它们都停止工作,并且在所有回调中设置的断点不会命中任何内容。 这也有点令人沮丧,因为它并不一致,有时我可以在几分钟后让它脱钩,有时它会保持钩子几个小时,所以调试起来很烦人。 @Davy8:你能用一个WH_KEYBOARD_LL钩子重现这个问题吗? 我遇到了类似的问题。为了进行测试,我附加了两个键盘挂钩:在我遇到断点后,只有一个被移除。在那种情况下,Windows 不应该删除所有钩子吗?另一个奇怪的是,安装的鼠标钩也被删除了。【参考方案3】:

这是一个很长的镜头,但你有没有运行防病毒软件?这很可能是注意到一个键盘挂钩并将其踢出。

它更有可能会警告您并立即将其删除,但这是值得检查的奇怪事情之一。

【讨论】:

【参考方案4】:

也许其他人有一个没有调用 CallNextHookEx() 的钩子?

【讨论】:

我不相信,因为当它进入这种状态时,钩子上的UnhookWindowsHookEx返回false,表明钩子已经消失了。【参考方案5】:

我知道这是一个丑陋的解决方案,但您可以将 Timer 设置为 5 分钟,然后您可以重新挂钩您的键盘事件?

我在 Win7 机器上遇到了同样的问题,一段时间后键盘挂钩失去了它的引用,我不得不每 5 分钟设置一个计时器来再次挂钩事件,现在它保持新鲜。

【讨论】:

【参考方案6】:

我正在使用来自http://www.codeproject.com/Articles/7294/Processing-Global-Mouse-and-Keyboard-Hooks-in-C 的以下项目在按下某个键时执行任务。

我注意到,在我使用热键执行任务后,它停止侦听新的按键,然后我将 KeyboardHookListener 更改为静态,它似乎解决了我的问题。我不知道为什么这解决了我的问题,所以请随时对此发表评论!

我的热键类:

 class Hotkey

    private static KeyboardHookListener _keyboardHookListener;

    public void Start()
    
        _keyboardHookListener = new KeyboardHookListener(new GlobalHooker())  Enabled = true ;
        _keyboardHookListener.KeyDown += KeyboardListener_OnkeyPress;
    

    private void KeyboardListener_OnkeyPress(object sender, KeyEventArgs e)
    
        // Let's backup all projects
        if (e.KeyCode == Keys.F1)
        
            // Initialize files
            var files = new Files();

            // Backup all projects
            files.BackupAllProjects();
        
        // Quick backup - one project
        else if (e.KeyCode == Keys.F2)
        
            var quickBackupForm = new QuickBackup();
            quickBackupForm.Show();
        
    

【讨论】:

【参考方案7】:

很晚,但我想我会分享一些东西。 从here 收集到一些提示,例如将 SetWindowsHookEx 放在单独的线程中,这更像是一个调度问题。

我还注意到 Application.DoEvents 使用了相当多的 CPU,并发现 PeekMessage 使用较少。

public static bool active = true;

[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage

    public IntPtr handle;
    public uint msg;
    public IntPtr wParam;
    public IntPtr lParam;
    public uint time;
    public System.Drawing.Point p;


[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool PeekMessage(out NativeMessage message,
    IntPtr handle, uint filterMin, uint filterMax, uint flags);
public const int PM_NOREMOVE = 0;
public const int PM_REMOVE = 0x0001;

protected void MouseHooker()

    NativeMessage msg;

    using (Process curProcess = Process.GetCurrentProcess())
    
        using (ProcessModule curModule = curProcess.MainModule)
        
            // Install the low level mouse hook that will put events into _mouseEvents
            _hookproc = MouseHookCallback;
            _hookId = User32.SetWindowsHookEx(WH.WH_MOUSE_LL, _hookproc, Kernel32.GetModuleHandle(curModule.ModuleName), 0);
        
    
        while (active)
        
            while (PeekMessage(out msg, IntPtr.Zero,
                (uint)WM.WM_MOUSEFIRST, (uint)WM.WM_MOUSELAST, PM_NOREMOVE))
                ;
            Thread.Sleep(10);
        

        User32.UnhookWindowsHookEx(_hookId);
        _hookId = IntPtr.Zero;            


public void HookMouse()

    active = true;
    if (_hookId == IntPtr.Zero)
    
        _mouseHookThread = new Thread(MouseHooker);
        _mouseHookThread.IsBackground = true;
        _mouseHookThread.Priority = ThreadPriority.Highest;
        _mouseHookThread.Start();
    

所以只需在 Deactivate 事件中将 active bool 更改为 false 并在 Activated 事件中调用 HookMouse。

HTH

编辑:我注意到游戏因此而变慢,因此决定在应用不活动时使用 Activated 和 Deactivate 事件取消挂钩。

【讨论】:

以上是关于什么会导致 Windows 取消挂钩低级别(全局)键盘挂钩?的主要内容,如果未能解决你的问题,请参考以下文章

我无法在提升的 Windows 应用程序中全局挂钩键盘

使用 UnhookWindowsHookEx() 取消挂钩时,多个程序崩溃

全局挂钩在 Visual C++ 中不起作用

单击标题栏按钮时,全局低级鼠标钩子会导致冻结

是否可以从 .NET 的全局键盘挂钩中确定当前用户

我可以使用全局系统挂钩来捕获单击了哪个文件吗?