Процедура перехвата нитей больше не вызывается после нескольких нажатий 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);
        }
    }
}

person Tu Le Hong    schedule 14.10.2012    source источник
comment
Ваш код отлично сработал для меня, и я нажимал вкладку более 19 раз. Можете ли вы предоставить какую-либо другую информацию? Как вы проверяете, что он не работает? Я только что посмотрел панель вывода в VS.   -  person nick_w    schedule 14.10.2012
comment
Я проверяю, что он работает, запустив в режиме отладки и посмотрев на окно вывода. Если хук работает, он должен записать WndProcHookCallback # в окно вывода, где # — возрастающее целое число. Если в окно вывода больше не записывается такое сообщение, процедура ловушки больше не вызывается.   -  person Tu Le Hong    schedule 14.10.2012
comment
Этот хук не делает ничего полезного. Как выглядит настоящий код обратного вызова ловушки?   -  person Hans Passant    schedule 14.10.2012


Ответы (2)


При тестировании вашей программы я получил следующую ошибку

Обнаружен 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.");
    }
}
person Scott Chamberlain    schedule 28.10.2013
comment
Ваш ответ правильно решил проблему. Спасибо! - person Tu Le Hong; 19.03.2014

Что может привести к отключению Windows хук клавиатуры уровня (глобальный)? покрывает это. Такая ситуация может возникнуть, если истекает время ожидания процедуры ловушки. Значение тайм-аута указано в ключе HKEY_CURRENT_USER\Control Panel\Desktop со значением LowLevelHooksTimeout (хотя в моей системе этого значения не было).

Из MSDN (есть также некоторая полезная информация в Содержимом сообщества на внизу этой страницы):

По истечении времени ожидания процедуры перехватчика система передает сообщение следующему перехватчику. Однако в Windows 7 и более поздних версиях хук удаляется без вызова.

Из Глобальные хуки теряются в окнах

В Windows 7 мы должны убедиться, что функция обратного вызова хука может вернуться менее чем через LowLevelHooksTimeout, что составляет 300 мс. И мы допускаем 10-кратный тайм-аут приложения при обработке сообщения обратного вызова ловушки. Если время ожидания истекает в 11-й раз, Windows отключит приложение от цепочки ловушек. Это особенность дизайна, и она была добавлена ​​в Win7 RTM.

На этой странице также предлагается использовать необработанный ввод.

Изменить: вот Учебник Code Project по использованию необработанного ввода в C#.

person nick_w    schedule 14.10.2012
comment
Мой крючок - крючок для ниток, а не крючок низкого уровня. Кроме того, процедура ловушки почти ничего не делает, кроме как выводит на консоль одну строку. Как это могло закончиться? - person Tu Le Hong; 14.10.2012