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

时间:2010-04-16 18:22:16

标签: c# keyboard-hook setwindowshookex

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

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

似乎没有一致的repro,我听说他们可以因为超时或异常被抛出而脱钩,但我已经尝试了两个只是让它坐在处理方法的断点上结束一分钟,以及抛出一个随机异常(C#),它似乎仍然有用。

在我们的回调中,我们很快发布到另一个线程,因此可能不是问题。我已经阅读了Windows 7中的解决方案,用于在注册表中设置更高的超时,因为Windows 7显然更加积极地执行超时(我们都在这里运行Win7,所以不确定这是否发生在其他操作系统上),但这并不是看起来似乎是一个理想的解决方案。

我认为只是偶尔运行一个后台线程来刷新钩子,这很黑,但我不知道这样做会带来什么真正的负面影响,而且似乎比改变全局更好Windows注册表设置。

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

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

7 个答案:

答案 0 :(得分:16)

我认为这必须是超时问题。

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

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

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

请记住,C#的一个荣耀是,如果发生垃圾收集,即使是简单的语句也会占用过多的时间。这(或其他线程的CPU加载)可能会解释问题的间歇性。

答案 1 :(得分:3)

我想到的有两件事可以帮助你找出问题所在。

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

  2. 通过回调记录挂钩的信息并运行它直到钩子脱钩。多次重复此操作并检查记录的数据,看看是否可以识别导致解除钩子的模式。

  3. 我会一次尝试这些,以防万一会影响其他人的结果。如果在那之后你没有找到问题所在的线索,你可以尝试一起运行它们。

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

我遇到了与Win7机器相同的问题,经过一段时间的键盘挂钩丢失了它的参考,我不得不每隔5分钟设置一个Timer来再次挂钩事件,现在它保持新鲜。

答案 5 :(得分:1)

我正在使用以下项目: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();
        }
    }
}

答案 6 :(得分:0)

很晚但想到我会分享一些东西。 从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事件中将活动bool更改为false,然后在Activated事件中调用HookMouse。

HTH

编辑:我注意到游戏因此放慢了速度,因此决定在使用“激活”和“停用”事件时应用程序处于非活动状态时取消挂钩。