小编典典

在WPF / C#中使用全局键盘挂钩(WH_KEYBOARD_LL)

c#

我将自己在Internet WH_KEYBOARD_LL助手类中找到的代码拼接在一起:

将以下代码放入您的某些utils库中,将其 命名YourUtils.cs

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace MYCOMPANYHERE.WPF.KeyboardHelper
{
    public class KeyboardListener : IDisposable
    {
        private static IntPtr hookId = IntPtr.Zero;

        [MethodImpl(MethodImplOptions.NoInlining)]
        private IntPtr HookCallback(
            int nCode, IntPtr wParam, IntPtr lParam)
        {
            try
            {
                return HookCallbackInner(nCode, wParam, lParam);
            }
            catch
            {
                Console.WriteLine("There was some error somewhere...");
            }
            return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
        }

        private IntPtr HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN)
                {
                    int vkCode = Marshal.ReadInt32(lParam);

                    if (KeyDown != null)
                        KeyDown(this, new RawKeyEventArgs(vkCode, false));
                }
                else if (wParam == (IntPtr)InterceptKeys.WM_KEYUP)
                {
                    int vkCode = Marshal.ReadInt32(lParam);

                    if (KeyUp != null)
                        KeyUp(this, new RawKeyEventArgs(vkCode, false));
                }
            }
            return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
        }

        public event RawKeyEventHandler KeyDown;
        public event RawKeyEventHandler KeyUp;

        public KeyboardListener()
        {
            hookId = InterceptKeys.SetHook((InterceptKeys.LowLevelKeyboardProc)HookCallback);
        }

        ~KeyboardListener()
        {
            Dispose();
        }

        #region IDisposable Members

        public void Dispose()
        {
            InterceptKeys.UnhookWindowsHookEx(hookId);
        }

        #endregion
    }

    internal static class InterceptKeys
    {
        public delegate IntPtr LowLevelKeyboardProc(
            int nCode, IntPtr wParam, IntPtr lParam);

        public static int WH_KEYBOARD_LL = 13;
        public static int WM_KEYDOWN = 0x0100;
        public static int WM_KEYUP = 0x0101;

        public static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                    GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr SetWindowsHookEx(int idHook,
            LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
    }

    public class RawKeyEventArgs : EventArgs
    {
        public int VKCode;
        public Key Key;
        public bool IsSysKey;

        public RawKeyEventArgs(int VKCode, bool isSysKey)
        {
            this.VKCode = VKCode;
            this.IsSysKey = isSysKey;
            this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
        }
    }

    public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args);
}

我这样使用:

App.xaml

<Application ...
    Startup="Application_Startup"
    Exit="Application_Exit">
    ...

App.xaml.cs

public partial class App : Application
{
    KeyboardListener KListener = new KeyboardListener();

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
    }

    void KListener_KeyDown(object sender, RawKeyEventArgs args)
    {
        Console.WriteLine(args.Key.ToString());
        // I tried writing the data in file here also, to make sure the problem is not in Console.WriteLine
    }

    private void Application_Exit(object sender, ExitEventArgs e)
    {
        KListener.Dispose();
    }
}

问题在于,它会 在按下按键一段时间后停止工作 。如此一来,不会出现任何错误,只是一段时间后我什么也没输出。当它停止工作时,我找不到稳定的模式。

重现此问题很简单,通常会在窗户外面,像疯子一样敲一些键。

我怀疑背后有一些邪恶的 线程问题 ,有人知道如何使它继续工作吗?


我已经尝试过的

  1. return HookCallbackInner(nCode, wParam, lParam);用简单的东西代替。
  2. 用异步调用替换它,尝试将Sleep设置为5000ms(等等)。

异步调用并没有使它更好地工作,它似乎总是在用户将单个字母保持一小段时间时停止。


阅读 1231

收藏
2020-05-19

共1个答案

小编典典

您正在SetHook方法调用中内联创建回调委托。那个委托最终将被垃圾收集,因为您没有在任何地方保留对它的引用。一旦委托被垃圾回收,您将不再获得任何回调。

为避免这种情况,只要挂钩就位,就需要保持对委托的引用(直到您调用UnhookWindowsHookEx)。

2020-05-19