c# - 多次按 Tab 键后不再调用线程 Hook 程序。为什么?

标签 c# .net winapi hook systemevent

我安装了一个线程特定的窗口钩子(Hook)来监视发送到 WndProc 的消息。一开始它起作用了。但是,在我按 Tab 键大约 19 次以围绕表单移动焦点后,我的 Hook 回调不再被调用。无论我是快速还是缓慢地按下 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);
        }
    }
}

最佳答案

在测试你的程序时我遇到了以下错误

CallbackOnCollectedDelegate was detected
Message: A callback was made on a garbage collected delegate of type 'Sandbox Form!Sandbox_Form.Program+HookProc::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

我认为问题是隐式创建以传递给 SetWindowsHookEx 以进行回调的委托(delegate)正在被垃圾收集。通过为委托(delegate)显式创建一个变量并将其保留在范围内,我认为这将使您的问题消失,当我将 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.");
    }
}

关于c# - 多次按 Tab 键后不再调用线程 Hook 程序。为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12878729/

相关文章:

c# - 如何减少页面加载时间(结合javascript)?

c# - 我的 C# 应用程序出现全屏错误

winapi - 使用 Windows GetDIBits 函数获取位图像素值

c++ - WS_CHILD 对话框上的 WS_TABSTOP

windows - Microsoft One Note 2010 是如何实现停靠到桌面的窗口的?

c# - 是否可以将某些行为绑定(bind)到类的任何动态属性?

c# - 如何确定 ParameterInfo 是否为返回参数

c# - 跨 Sharepoint 服务器上所有站点和子站点的 Linq 查询?

c# - 锁与比较和交换

C# CLR/编译问题