【问题标题】:Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C#在 WPF / C# 中使用全局键盘挂钩 (WH_KEYBOARD_LL)
【发布时间】:2010-12-10 23:49:56
【问题描述】:

我将自己在互联网上找到的代码拼接在一起 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. 用异步调用替换它,尝试将睡眠 5000 毫秒(等)。

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

【问题讨论】:

  • 我们可以截取密钥并发送一个不同的密钥而不是按下的那个吗?例如按 a 发送键 e 。
  • 完整、可用并记录在案。这就是我喜欢 StackOverflow 的地方
  • 是的,确实是新版本。我将添加到正文的链接。
  • 我的代码和 USB 读卡器有问题。有时键没有右移。在同一会话中,我可以阅读一次 %WHATEVERò1234_ 和下一次 5WHAteVERò!"34_ (其中 ò 应该是 ; 和 _ 应该是?,但这取决于我的键盘)。在我编写自己的虚拟键解析器之前有什么帮助吗?
  • 不确定这里发生了什么。 Matt 的修订已将其恢复为仍受垃圾回收影响的版本。检查早期的修订; 15 代表 Ciantic 的最后一个,或 16 代表 Matt 的第一个,我更喜欢命名。 17 和 18 是损坏的版本。

标签: c# wpf winapi


【解决方案1】:

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

为防止这种情况发生,只要钩子存在(直到您调用 UnhookWindowsHookEx),您就需要保持对委托的引用。

【讨论】:

  • 是的!你完全正确,我会解决这个问题,因为其他人可能会来这里思考,希望你不介意......
  • 我们可以截取密钥并发送一个不同的密钥而不是按下的那个吗?例如按 a 发送键 e 。
  • 我不知道为什么,但这似乎在 Microsoft Visual Studio 2010 的调试模式下不起作用。有什么想法吗?
【解决方案2】:

IIRC,在使用全局挂钩时,如果您的 DLL 没有足够快地从回调中返回,那么您将从回调链中移除。

因此,如果您说它可以工作一段时间,但如果您输入得太快它会停止工作,我可能会建议将密钥存储到内存中的某个位置,然后再转储密钥。例如,您可能会检查某些键盘记录器的来源,因为它们使用相同的技术。

虽然这可能无法直接解决您的问题,但至少应该排除一种可能性。

您是否考虑过使用GetAsyncKeyState 而不是全局挂钩来记录击键?对于您的应用程序,这可能就足够了,有很多完全实现的示例,而且个人更容易实现。

【讨论】:

  • 不,这应该是通用的 sn-pter 应用程序。 GetAsyncKeyState 不会...这应该像键盘嗅探器一样工作。顺便说一句,你的语气很贬低,我在 C 语言中也做过同样的事情,它的工作方式就像是注定的那样。但我会听从你的建议,研究一下这个“足够快”的事情。
  • 如果我的语气带有侮辱性,我深表歉意,这不是故意的。我只知道由于线程/链/存储问题,使用 GetAsyncKeyState 作为我的键盘记录器比使用全局挂钩要简单得多。
  • GetAsyncKeyState 在多任务操作系统中不起作用。当没有其他程序可以调用 GetAsyncKeyState 并接收“最近按下”位而不是您的应用程序时,它是为 win3.x 应用程序设计的。
  • @Shengjiang,这是一个很好的观点。不过,根据经验,它对于我的目的通常已经足够了,而且许多使用键盘记录器的项目似乎都在使用它,包括 Metasploit 的 Meterpreter。较低的内存占用也不错。
  • 我发现 GetAsyncKeyState 是从 LL 回调中正确检索完整击键信息的唯一方法,尤其是当另一个应用程序是前台窗口时的 shift、control 和 alt 状态。 GetKeyboardState 将错误地报告这些修饰键值,因为它具有线程关联性。另一方面,GetAsyncKeyState 不返回 caps、scroll 和 num 的切换值。我实际上是这样做的。完全避免 GetKeyboardState。循环调用 GetAsyncKeyState 的所有键。只为 caps、num 和 scroll 调用 GetKeyState,检查切换位。
【解决方案3】:

获胜者是:Capture Keyboard Input in WPF,建议这样做:

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

...然后只需使用事件处理程序参数的 Text 属性:

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

【讨论】:

  • 仅在窗口获得焦点时有效。
【解决方案4】:

我使用Dylan's method 来挂钩 WPF 应用程序中的全局关键字,并在每次按键后刷新挂钩,以防止事件在几次点击后停止触发。 IDK,无论是好的还是坏的做法,但都能完成工作。

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

实现细节here

【讨论】:

    【解决方案5】:

    我真的在找这个。感谢您在此处发布此内容。
    现在,当我测试您的代码时,我发现了一些错误。代码起初不起作用。它无法处理两个按钮点击,即: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% 工作 :) 希望对你有帮助

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-23
      • 1970-01-01
      • 1970-01-01
      • 2020-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多