Использование глобального крючка клавиатуры (WH KEYBOARD LL) в WPF / C#


Я сшил вместе из кода, который я нашел в интернете сам 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);
}

который я использую вот так:

приложение.xaml:

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

приложение.код 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. заменив его с асинхронный вызов, пытаясь поставить 5000 мс сон (и т. д.).

асинхронный вызов не сделал его работу лучше, кажется, остановить всегда, когда пользователь держит одну букву вниз на некоторое время.

5 53

5 ответов:

вы создаете свой делегат обратного вызова встроенный в вызов метода SetHook. Этот делегат в конечном итоге получит сбор мусора, так как вы нигде не сохраняете ссылку на него. И как только делегат будет собран мусор, вы больше не получите обратных вызовов.

чтобы предотвратить это, вам нужно сохранить ссылку на делегат живым, пока крюк находится на месте (пока вы не вызовете UnhookWindowsHookEx).

победитель: захват ввода с клавиатуры в WPF, который предполагает делать :

TextCompositionManager.AddTextInputHandler(this,
    new TextCompositionEventHandler(OnTextComposition));

...а затем просто используйте текстовое свойство аргумента обработчика событий:

private void OnTextComposition(object sender, TextCompositionEventArgs e)
{
    string key = e.Text;
    ...
}

IIRC, при использовании глобальных крючков, Если ваша DLL не возвращается из обратного вызова достаточно быстро, вы удаляетесь из цепочки обратных вызовов.

поэтому, если вы говорите, что он работает немного, но если вы печатаете слишком быстро, он перестает работать, я мог бы предложить просто хранить ключи в каком-то месте в памяти и сбрасывать ключи позже. Например, вы можете проверить источник для некоторых кейлоггеров, так как они используют ту же технику.

хотя это может не решить ваша проблема напрямую, она должна по крайней мере исключить одну возможность.

вы думали об использовании GetAsyncKeyState вместо глобального крючка для регистрации нажатий клавиш? Для вашего приложения этого может быть достаточно, есть много полностью реализованных примеров, и лично было проще реализовать.

Я действительно искал это. Спасибо, что разместили это здесь.
Теперь, когда я проверил ваш код, я нашел несколько ошибок. Сначала код не работал. И он не мог справиться с двумя кнопками нажмите т. е.:CTRL + P.
То, что я изменил, это те значения, которые выглядят ниже:
private void HookCallbackInner to

private void 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));
                }
            }
        }

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using FileManagerLibrary.Objects;

namespace FileCommandManager
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        readonly KeyboardListener _kListener = new KeyboardListener();
        private DispatcherTimer tm;

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

        private List<Key> _keysPressedIntowSecound = new List<Key>();
        private void TmBind()
        {
            tm = new DispatcherTimer();
            tm.Interval = new TimeSpan(0, 0, 2);
            tm.IsEnabled = true;
            tm.Tick += delegate(object sender, EventArgs args)
            {
                tm.Stop();
                tm.IsEnabled = false;
                _keysPressedIntowSecound = new List<Key>();
            };
            tm.Start();
        }

        void KListener_KeyDown(object sender, RawKeyEventArgs args)
        {
            var text = args.Key.ToString();
            var m = args;
            _keysPressedIntowSecound.Add(args.Key);
            if (tm == null || !tm.IsEnabled)
                TmBind();
        }

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

этот код работает 100% в windows 10 для меня :) Я надеюсь, что это поможет U

я использовал метод Дилана чтобы зацепить глобальное ключевое слово в приложении WPF и обновить крюк после каждого нажатия клавиши, чтобы предотвратить прекращение событий после нескольких щелчков мыши. IDK, если это хорошая или плохая практика, но выполняет свою работу.

      _listener.UnHookKeyboard();
      _listener.HookKeyboard();

детали реализации здесь