Процедура зацепления нити больше не вызывается после нескольких нажатий клавиши Tab. Зачем?
Я установил потоковую ловушку 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);
}
}
}
2 ответа
При тестировании вашей программы я получил следующую ошибку
CallbackOnCollectedDelegate был обнаружен
Сообщение: был выполнен обратный вызов делегата со сборщиком мусора типа "Форма песочницы!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 отсоединит приложение от цепочки подключений. Это особенность, добавленная в Win7 RTM.
На этой странице также предлагается использовать Raw Input.
Редактировать: вот учебник Code Project по использованию необработанного ввода в C#.