web-dev-qa-db-ja.com

WPF / C#でグローバルキーボードフック(WH_KEYBOARD_LL)を使用する

私は自分でインターネットで見つけたコードから一緒にステッチしました_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. 非同期呼び出しに置き換えて、スリープ5000msなどを設定しようとします。

非同期呼び出しはそれを上手く機能させませんでした。ユーザーがしばらく一文字を押し続けると常に停止するようです。

56
Ciantic

SetHookメソッド呼び出しでコールバックデリゲートをインラインで作成しています。そのデリゲートは、どこでも参照を保持していないため、最終的にガベージコレクションを取得します。そして、デリゲートがガベージコレクションされると、コールバックを取得できなくなります。

これを防ぐには、フックが設定されている限り(UnhookWindowsHookExを呼び出すまで)、デリゲートへの参照を保持する必要があります。

18
Mattias S

勝者は次のとおりです。 WPFでキーボード入力をキャプチャ

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

...次に、イベントハンドラー引数のTextプロパティを使用します。

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

IIRC、グローバルフックを使用しているときに、DLLがコールバックから十分な速さで復帰しない場合、コールバックのチェーンから削除されます。

したがって、少しの間は機能していると言っているが、あまりにも速く入力すると動作が停止する場合、キーをメモリの特定の場所に保存し、後でキーをダンプすることをお勧めします。たとえば、同じ手法を使用するキーロガーのソースを確認できます。

これは問題を直接解決しないかもしれませんが、少なくとも1つの可能性を除外する必要があります。

キーストロークを記録するために、グローバルフックの代わりにGetAsyncKeyStateを使用することを考えましたか?あなたのアプリケーションにとっては、それで十分かもしれませんし、たくさんの完全に実装された例があり、個人的には実装が簡単でした。

1
mrduclaw

私はこれを本当に探していました。こちらに投稿していただきありがとうございます。
今、あなたのコードをテストしたところ、いくつかのバグが見つかりました。コードは最初は機能しませんでした。そして、2つのボタンクリックを処理できませんでした:つまり: CTRL + 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();
        }
    }
}

このコードは私にとってWindows 10で100%動作します:)

0
Alen.Toma

Dylanのメソッド を使用して、WPFアプリケーションでグローバルキーワードをフックし、キーを押すたびにフックを更新して、数回クリックした後にイベントが発生しないようにしました。 IDK、それが良いか悪い練習であるが、仕事を終わらせる場合。

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

実装の詳細 ここ

0
EvilInside