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

我们通过SetWindowsHookExWH_KEYBOARD_LL安装了一些全局键盘钩子,这些钩子看起来随机被Windows WH_KEYBOARD_LL

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

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

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

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

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

编辑:通过调用GC.Collect();validationGC.Collect(); 他们仍然工作,所以他们没有被收集。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

很晚但我以为我会分享一些东西。 从这里收集有一些提示,比如将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

编辑:我注意到这个游戏的速度变慢了,所以当使用Activated和Deactivate事件激活应用程序不活动时,决定取消挂钩。