使用SetWindowsHookEx设置的低级键盘挂钩停止在C#中调用函数

时间:2011-07-22 22:34:50

标签: c# multithreading low-level keyboard-hook setwindowshookex

我正在创建一个监控按键的程序,以便全局控制iTunes。它还有一些WinForms(用于显示曲目信息和编辑选项)。

低级键盘挂钩很有用。如果我刚启动程序,则设置键盘挂钩并打开iTunes。然后我打开记事本并且可以非常快速地输入大量的东西并捕获每个笔划,在钩子函数中花费最多30ms(并且大部分<10ms)。钩子函数只是将事件添加到由另一个线程处理的队列中。它使用自己的Application.Run()运行在自己的高优先级线程上。

但是,如果我开始在iTunes中做事(例如在我的程序中生成事件的几个播放/暂停点击)或在程序内(比如打开选项窗口),那么钩子函数就会停止被调用!即使从未使用过键盘,也会发生这种情况(例如,启动,点击播放并在iTunes中暂停几次,然后按一个键)。

未调用钩子的原因不是因为在钩子函数中花费了太多时间。

当我调用UnhookWindowsHookEx时,它总是返回true,无论是否仍在调用钩子函数。

那么,原因可能是什么?

一个想法(虽然我没有证据或解决方案)是托管线程不再是正确的本机线程。我在程序中使用了大量(托管)线程,并且我已经读过单个本机线程可以运行许多托管线程,并且托管线程可以更改正在运行它的本机线程。钩子是否仍然可以生成消息但是将它们发送到错误的线程?如果是这种情况,我该如何解决它?


编辑:挂钩和回调

我的KeyMonitor稍微剥离完成版本。为了清楚起见,它被剥离了。我删除了一些实用程序(像Key枚举的大多数值和Keys类的许多函数,如ToString()和FromString())以及一些错误处理。

大多数重要的东西都在KeyMonitor类中。 KeyMonitor.Start()为消息启动一个线程,KeyMonitor.HookThread()是该线程,并为消息循环创建钩子和Application.Run(),KeyMonitor.KeyboardHookProc()是回调函数和KeyMonitor。 HookEventDispatchThread()用于调度回调记录的事件。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace KeyTest
{
    enum Key : int
    {
        Shift = 0x10, Ctrl, Alt,
        Left_Win = 0x5B, Right_Win,
        Left_Shift = 0xA0, Right_Shift, Left_Ctrl, Right_Ctrl, Left_Alt, Right_Alt,
    }

    class Keys
    {
        [DllImport("user32.dll")]
        private static extern int GetKeyboardState(byte[] pbKeyState);
        public const int Count = 256; // vkCode are from 1 to 254, but GetKeyboardState uses 0-255

        private readonly bool[] keys = new bool[Count];

        public Keys() { }

        private void DoModifier(Key x, Key l, Key r) { keys[(int)x] = keys[(int)l] || keys[(int)r]; }
        private void DoModifiers()
        {
            DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift);
            DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl);
            DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt);
        }
        private void DoModifier(Key x, Key l, Key r, Key k) { if (k == l || k == r) keys[(int)x] = keys[(int)l] || keys[(int)r]; }
        private void DoModifiers(Key k)
        {
            DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift, k);
            DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl, k);
            DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt, k);
        }

        public bool this[int i] { get { return this.keys[i]; } set { this.keys[i] = value; DoModifiers((Key)i); } }
        public bool this[Key k] { get { return this.keys[(int)k]; } set { this.keys[(int)k] = value; DoModifiers(k); } }

        public void LoadCurrentState()
        {
            byte[] keyState = new byte[Count];
            if (GetKeyboardState(keyState) != 0)
                for (int i = 0; i < Count; ++i)
                    keys[i] = (keyState[i] & 0x80) != 0;
            DoModifiers();
        }
    }

    static class KeyMonitor
    {
        #region Windows API
        private delegate int HookProc(int nCode, UIntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
        [DllImport("user32.dll", SetLastError = true)]
        private static extern int UnhookWindowsHookEx(int idHook);
        [DllImport("user32.dll")]
        private static extern int CallNextHookEx(int idHook, int nCode, UIntPtr wParam, IntPtr lParam);

        private const int WH_KEYBOARD_LL = 13;
        private readonly static UIntPtr WM_KEYDOWN = new UIntPtr(0x100), WM_SYSKEYDOWN = new UIntPtr(0x104);
        #endregion

        public static event KeyEventHandler OverridingKeyChange;
        public static event KeyEventHandler KeyChange;

        private struct KeyEventData { public int vk; public bool down; }

        private static int hook = 0;
        private static Thread dispatchThread = null, hookThread = null;
        private static Keys keys = new Keys();
        private static Queue<KeyEventData> queue = new Queue<KeyEventData>();

        private static void Enqueue(int vk, bool down)
        {
            lock (queue)
            {
                queue.Enqueue(new KeyEventData() { vk = vk, down = down });
                Monitor.Pulse(queue);
            }
        }
        public static Keys Keys { get { return keys; } }

        public static void Start()
        {
            if (hook == 0)
            {
                dispatchThread = new Thread(HookEventDispatchThread);
                hookThread = new Thread(HookThread);
                hookThread.Priority = ThreadPriority.Highest;
                dispatchThread.Start();
                hookThread.Start();
            }
        }

        public static void Stop()
        {
            if (hook != 0)
            {
                // Minimal cleanup...
                UnhookWindowsHookEx(hook);
                Application.Exit();
                dispatchThread.Interrupt();
            }
        }

        private static void HookThread()
        {
            hook = SetWindowsHookEx(WH_KEYBOARD_LL, new HookProc(KeyboardHookProc), IntPtr.Zero, 0);
            if (hook == 0) { /* Handle error */ }
            keys.LoadCurrentState();
            Application.Run();
        }

        private static int KeyboardHookProc(int nCode, UIntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
                Enqueue(Marshal.ReadInt32(lParam), wParam == WM_SYSKEYDOWN || wParam == WM_KEYDOWN);
            return CallNextHookEx(hook, nCode, wParam, lParam);
        }

        private static void HookEventDispatchThread()
        {
            for (; ; )
            {
                KeyEventData data;
                lock (queue)
                {
                    if (queue.Count == 0)
                        try
                        {
                            Monitor.Wait(queue);
                        }
                        catch (ThreadInterruptedException) { return; }
                    data = queue.Dequeue();
                }
                if (data.vk == -1)
                {
                    // Done!
                    keys = new Keys();
                    queue.Clear();
                    return;
                }
                else if (keys[data.vk] == data.down)
                    continue;

                keys[data.vk] = data.down;
                KeyEventArgs e = new KeyEventArgs((System.Windows.Forms.Keys)data.vk);
                if (OverridingKeyChange != null) OverridingKeyChange(null, e);
                if (!e.Handled && KeyChange != null) KeyChange(null, e);
            }
        }
    }

}

1 个答案:

答案 0 :(得分:4)

您需要将代理保存到一个在应用程序持续时间内存活的变量。否则,委托是垃圾收集的(奇怪的是应用程序没有崩溃!)。

static HookProc hookProc;
...

hookProc = new HookProc(KeyboardHookProc);
hook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, IntPtr.Zero, 0);