我安装了一个特定于线程的Windows挂钩来监视发送到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);
/// <summary>
/// The main entry point for the application.
/// </summary>
[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);
}
}
}
答案 0 :(得分:3)
在测试程序时,我收到以下错误
检测到CallbackOnCollectedDelegate
消息:进行了回调 在垃圾收集的代表'沙盒类型 表格!Sandbox_Form.Program + HOOKPROC ::调用”。这可能会导致 应用程序崩溃,损坏和数据丢失。通过代表时 对于非托管代码,它们必须由托管应用程序保持活动状态 直到它保证永远不会被召唤。
我认为问题是隐式创建的委托被传递给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.");
}
}
答案 1 :(得分:0)
What can cause Windows to unhook a low level (global) keyboard hook?涵盖了这一点。如果挂钩过程超时,则会发生这种情况。超时值在HKEY_CURRENT_USER\Control Panel\Desktop
键处指定,值为LowLevelHooksTimeout
(尽管我的系统中没有此值)。
来自MSDN(本页底部的社区内容中也有一些很好的信息):
如果挂钩过程超时,系统会将消息传递给 下一个钩子。但是,在Windows 7及更高版本中,挂钩是静默的 删除没有被调用。
来自Global hooks getting lost on windows
在Windows 7上,我们必须确保挂钩的回调函数可以在低于LowLevelHooksTimeout(300 ms)的时间内返回。并且我们允许在处理钩子回调消息时将应用程序超时10次。如果它超时第11次,Windows将从挂钩链中取消挂钩应用程序。这是一个按设计的功能,它已添加到Win7 RTM中。
该页面还建议改为使用Raw Input。
修改:在{C#中使用原始输入时,Code Project tutorial为{{3}}。