按Tab键几次后不再调用线程钩子程序。 为什么?

我安装了一个特定于线程的窗口挂钩来监视发送到WndProc的消息。 它起初工作。 然而,在我按Tab键约19次以在表单周围移动焦点后,我的钩子回调不再被调用。 这种情况发生了我是快速还是慢速按Tab键的问题。 谁能解释一下究竟发生了什么?

下面是我写的代码。 我在Windows 7 64位上测试过它。

using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.Runtime.InteropServices; namespace HookTest { static class Program { private const int WH_CALLWNDPROC = 4; private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); private class MainForm : Form { private Button button1; private TextBox textBox1; public MainForm() { this.button1 = new System.Windows.Forms.Button(); this.textBox1 = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // // button1 // this.button1.Location = new System.Drawing.Point(12, 38); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(75, 23); this.button1.TabIndex = 0; this.button1.Text = "Button 1"; this.button1.UseVisualStyleBackColor = true; // // textBox1 // this.textBox1.Location = new System.Drawing.Point(12, 12); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(100, 20); this.textBox1.TabIndex = 1; // // MainForm // this.Controls.Add(this.textBox1); this.Controls.Add(this.button1); this.Name = "MainForm"; this.Text = "Main Form"; this.ResumeLayout(false); this.PerformLayout(); } } private static IntPtr hWndProcHook = IntPtr.Zero; private static int messageCount = 0; [DllImport("Kernel32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("Kernel32.dll", CharSet = CharSet.Auto)] public static extern uint GetCurrentThreadId(); [DllImport("User32.dll", CharSet = CharSet.Auto)] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("User32.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("User32.dll", CharSet = CharSet.Auto)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); ///  /// The main entry point for the application. ///  [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); InstallHook(); Application.Run(new MainForm()); UninstallHook(); } private static void InstallHook() { if (Program.hWndProcHook == IntPtr.Zero) { Console.WriteLine("Hooking..."); Program.hWndProcHook = SetWindowsHookEx( WH_CALLWNDPROC, WndProcHookCallback, GetModuleHandle(null), GetCurrentThreadId()); if(Program.hWndProcHook != IntPtr.Zero) Console.WriteLine("Hooked successfully."); else Console.WriteLine("Failed to hook."); } } private static void UninstallHook() { if (Program.hWndProcHook != IntPtr.Zero) { Console.WriteLine("Unhooking..."); if (UnhookWindowsHookEx(Program.hWndProcHook)) Console.WriteLine("Unhooked successfully."); else Console.WriteLine("Failed to unhook."); Program.hWndProcHook = IntPtr.Zero; } } private static IntPtr WndProcHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { Console.WriteLine("WndProcHookCallback {0}", Program.messageCount++); return CallNextHookEx(Program.hWndProcHook, nCode, wParam, lParam); } } } 

在测试您的程序时,我收到以下错误

检测到CallbackOnCollectedDelegate
消息:在“Sandbox Form!Sandbox_Form.Program + HookProc :: Invoke”类型的垃圾收集委托上进行了回调。 这可能会导致应用程序崩溃,损坏和数据丢失。 将委托传递给非托管代码时,托管应用程序必须保持它们的活动状态,直到确保它们永远不会被调用。

我认为问题是隐式创建的委托被传递给SetWindowsHookEx ,因为回调是垃圾收集。 通过为委托显式创建变量并将其保留在范围内,我认为它会使您的问题消失,当我将InstallHook修改为以下内容时,我无法再重新创建错误。

 private static HookProc hookProcDelegate; private static void InstallHook() { if (Program.hWndProcHook == IntPtr.Zero) { Console.WriteLine("Hooking..."); hookProcDelegate = new HookProc(WndProcHookCallback); Program.hWndProcHook = SetWindowsHookEx( WH_CALLWNDPROC, hookProcDelegate, GetModuleHandle(null), GetCurrentThreadId()); if (Program.hWndProcHook != IntPtr.Zero) Console.WriteLine("Hooked successfully."); else Console.WriteLine("Failed to hook."); } } 

什么可以导致Windows取消挂钩低级别(全局)键盘钩子? 涵盖了这个。 如果挂钩过程超时,则会发生这种情况。 超时值在HKEY_CURRENT_USER\Control Panel\Desktop键中指定,值为LowLevelHooksTimeout (尽管我的系统上没有此值)。

来自MSDN (本页底部的社区内容中也有一些很好的信息):

如果挂钩过程超时,系统会将消息传递给下一个挂钩。 但是,在Windows 7及更高版本中,挂钩会在不调用的情况下以静默方式删除。

从全球钩子迷失在窗户上

在Windows 7上,我们必须确保钩子的回调函数可以在低于LowLevelHooksTimeout(300毫秒)的时间内返回。 并且我们允许在处理钩子回调消息时将应用程序超时10次。 如果它超时第11次,Windows将从挂钩链中取消挂钩应用程序。 这是一个按设计function,它已添加到Win7 RTM中。

该页面还建议使用Raw Input 。

编辑:这是关于在C#中使用原始输入的代码项目教程 。