Процедурата за закачане на нишка вече не се извиква след натискане на Tab няколко пъти. Защо?

Инсталирах специфична за нишка кука за Windows, за да наблюдавам съобщенията, изпратени до WndProc. Отначало проработи. Въпреки това, след като натиснах Tab около 19 пъти, за да преместя фокуса около формуляр, моето обратно извикване на кука вече не се извиква. Това се случи независимо от това дали натиснах Tab бързо или бавно. Може ли някой да обясни какво всъщност се случва?

По-долу е кодът, който написах. Тествах го на Windows 7 64 bit.

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
Вашият код работи добре за мен и натиснах tab много повече от 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

В Windows 7 трябва да сме сигурни, че функцията за обратно извикване на куката може да се върне за по-малко от LowLevelHooksTimeout, което е 300 ms. И ние позволяваме приложението да бъде изтекло 10 пъти при обработка на съобщението за обратно извикване на куката. Ако времето за изчакване изтече 11-ти път, Windows ще откачи приложението от веригата за кука. Това е функция по дизайн и е добавена в Win7 RTM.

Тази страница също предлага вместо това да се използва Необработен вход.

Редактиране: тук е Урок за проект на код за използване на необработен вход в C#.

person nick_w    schedule 14.10.2012
comment
Моята кука е кука с конец, а не кука с ниско ниво. В допълнение, процедурата за закачане не прави почти нищо, освен да напише един ред в конзолата. Как може да изтече? - person Tu Le Hong; 14.10.2012