programing

WPF/C#에서 전역 키보드 후크(WH_KEYBOARD_LL)를 사용합니다.

yellowcard 2023. 4. 25. 22:13
반응형

WPF/C#에서 전역 키보드 후크(WH_KEYBOARD_LL)를 사용합니다.

에서 찾은 코드에서 직접 연결했습니다.WH_KEYBOARD_LL다음을 수행합니다.

다음 코드를 utils libs에 넣으십시오. 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. 체체중중다 replacing replacing replacing replacing를 바꿉니다.return HookCallbackInner(nCode, wParam, lParam);네, 그렇습니다.
  2. 비동기 호출로 대체하고, Sleep 5000ms를 설정하려고 합니다.

비동기 통화는 더 잘 작동되지 않습니다. 사용자가 한 글자를 잠시 동안 줄이면 항상 작동이 중지되는 것 같습니다.

SetHook 메서드 호출에서 인라인으로 콜백 대리인을 만들고 있습니다.그 대리인은 결국 쓰레기 수집을 하게 됩니다. 왜냐하면 당신은 아무데도 참조를 보관하지 않기 때문입니다.위임자가 가비지를 수집하면 더 이상 콜백을 받을 수 없습니다.

이를 방지하려면 후크가 있는 한(Windows를 호출할 때까지) 대리인에 대한 참조를 활성 상태로 유지해야 합니다.HookEx)를 선택합니다.

WPF에서 키보드 입력 캡처(Capture Keyboard Input in WPF)는 다음과 같은 이점을 제공합니다.

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

...이벤트 핸들러 인수의 Text 속성을 사용합니다.

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

IIRC에서 전역 후크를 사용할 때 DLL이 콜백에서 충분히 빨리 반환되지 않으면 콜백 체인에서 제거됩니다.

따라서 잠시 작동하지만 너무 빨리 입력하면 작동이 중지되는 경우, 메모리에 키를 저장하고 나중에 키를 덤핑하는 것이 좋습니다.예를 들어 일부 키로거에서는 동일한 기술을 사용하므로 소스를 확인할 수 있습니다.

이것이 문제를 직접 해결하지는 못할 수도 있지만, 적어도 한 가지 가능성은 배제해야 합니다.

여러분도 한번 써보신 적 있나요?GetAsyncKeyState키 입력을 기록하는 글로벌 훅 대신이요?애플리케이션의 경우, 충분히 충분할 수 있고, 완벽하게 구현된 예가 많고, 개인적으로 구현하기가 더 쉬웠습니다.

Dylan의 방법을 사용하여 WPF 어플리케이션에서 글로벌 키워드를 후크하고 키를 누를 때마다 리프레쉬 훅을 사용하여 클릭 몇 번 후 이벤트 발생을 방지하였습니다. IDK, 좋든 나쁘든 간에 작업을 완료하면 됩니다.

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

구현 세부 정보는 여기를 참조하십시오.

정말 이걸 찾고 있었어요.이 글을 올려주셔서 감사합니다.
코드를 테스트해보니 버그가 몇 개 발견되었습니다.처음에는 코드가 작동하지 않았습니다.그리고 두 개의 버튼 클릭(P예: +)을 처리할 수 없었습니다.
변경된 값은 다음과 같습니다.
private void HookCallbackInner로.

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

이 코드는 윈도우 10에서 100% 작동합니다. :) 이것이 도움이 되었으면 합니다.

언급URL : https://stackoverflow.com/questions/1639331/using-global-keyboard-hook-wh-keyboard-ll-in-wpf-c-sharp 입니다.

반응형