4

WndProcに送信されるメッセージを監視するために、スレッド固有のWindowsフックをインストールしました。最初はうまくいきました。ただし、Tabキーを約19回押してフォーム内でフォーカスを移動した後、フックコールバックは呼び出されなくなりました。これは、Tabキーをすばやく押すかゆっくり押すかに関係なく発生しました。誰かが実際に何が起こっているのか説明できますか?

以下は私が書いたコードです。Windows764ビットでテストしました。

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);
        }
    }
}
4

2 に答える 2

3

プログラムのテスト中に、次のエラーが発生しました

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.");
    }
}
于 2013-10-28T03:40:48.973 に答える
0

Windowsが低レベル(グローバル)キーボードフックを解除する原因は何ですか?これをカバーしています。この状況は、フック手順がタイムアウトした場合に発生する可能性があります。タイムアウト値はHKEY_CURRENT_USER\Control Panel\Desktop、値とともにキーで指定されますLowLevelHooksTimeout(ただし、この値は私のシステムには存在しませんでした)。

MSDNから(このページの下部にあるコミュニティコンテンツにもいくつかの良い情報があります):

フック手順がタイムアウトした場合、システムはメッセージを次のフックに渡します。ただし、Windows 7以降では、フックは呼び出されずにサイレントに削除されます。

Windowsで失われるグローバルフックから

Windows 7では、フックのコールバック関数がLowLevelHooksTimeout(300ミリ秒)未満で戻ることができることを確認する必要があります。また、フックコールバックメッセージを処理するときに、アプリケーションが10回タイムアウトすることを許可します。11回目のタイムアウトになると、Windowsはアプリケーションをフックチェーンから外します。これは設計による機能であり、Win7RTMで追加されました。

このページでは、代わりにRawInputを使用することも提案しています。

編集:これは、C#でのRaw入力の使用に関するCodeProjectチュートリアルです。

于 2012-10-14T03:38:20.337 に答える