在 WPF/C# 中使用全局键盘钩子 (WH_KEYBOARD_LL)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1639331/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C#
提问by Ciantic
I stitched together from code I found in internet myself WH_KEYBOARD_LL
helper class:
我从我在互联网上找到的代码拼接在一起自己的WH_KEYBOARD_LL
助手类:
Put the following code to some of your utils libs, let it be YourUtils.cs:
将以下代码放入您的一些 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);
}
Which I use like this:
我像这样使用:
App.xaml:
应用程序.xaml:
<Application ...
Startup="Application_Startup"
Exit="Application_Exit">
...
App.xaml.cs:
应用程序.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();
}
}
The problem is that it stops working after hitting keys a while. No error is raised what so ever, I just don't get anything to output after a while. I can't find a solid pattern when it stops working.
问题是它在按一下键后停止工作。没有任何错误被提出,我只是在一段时间后没有得到任何输出。当它停止工作时,我找不到固定的模式。
Reproducing this problem is quiet simple, hit some keys like a mad man, usually outside the window.
重现这个问题很简单,像疯子一样敲击一些键,通常在窗外。
I suspect there is some evil threading problembehind, anyone got idea how to keep this working?
我怀疑背后有一些邪恶的线程问题,有人知道如何保持这个工作吗?
What I tried already:
我已经尝试过的:
- Replacing
return HookCallbackInner(nCode, wParam, lParam);
with something simple. - Replacing it with asynchronous call, trying to put Sleep 5000ms (etc).
return HookCallbackInner(nCode, wParam, lParam);
用简单的东西代替。- 用异步调用替换它,尝试将 Sleep 5000ms (等)。
Asynchronous call didn't make it work any better, it seems stop always when user keeps single letter down for a while.
异步调用并没有让它工作得更好,当用户保持单个字母一段时间时,它似乎总是停止。
采纳答案by Mattias S
You're creating your callback delegate inline in the SetHook method call. That delegate will eventually get garbage collected, since you're not keeping a reference to it anywhere. And once the delegate is garbage collected, you will not get any more callbacks.
您正在 SetHook 方法调用中创建内联回调委托。该委托最终将被垃圾收集,因为您没有在任何地方保留对它的引用。一旦委托被垃圾收集,您将不会再收到任何回调。
To prevent that, you need to keep a reference to the delegate alive as long as the hook is in place (until you call UnhookWindowsHookEx).
为防止这种情况发生,只要挂钩就位(直到您调用 UnhookWindowsHookEx),您就需要保持对委托的引用。
回答by mrduclaw
IIRC, when using global hooks, if your DLL isn't returning from the callback quick enough, you're removed from the chain of call-backs.
IIRC,当使用全局钩子时,如果你的 DLL 没有足够快地从回调中返回,你就会从回调链中删除。
So if you're saying that its working for a bit but if you type too quickly it stops working, I might suggest just storing the keys to some spot in memory and the dumping the keys later. For an example, you might check the source for some keyloggers since they use this same technique.
因此,如果您说它可以工作一段时间,但如果您键入得太快,它就会停止工作,我可能建议您将密钥存储在内存中的某个位置,然后再转储这些密钥。例如,您可能会检查某些键盘记录器的来源,因为它们使用相同的技术。
While this may not solve your problem directly, it should at least rule out one possibility.
虽然这可能无法直接解决您的问题,但至少应该排除一种可能性。
Have you thought about using GetAsyncKeyState
instead of a global hook to log keystrokes? For your application, it might be sufficient, there's lots of fully implemented examples, and was personally easier to implement.
你有没有想过使用GetAsyncKeyState
而不是全局钩子来记录击键?对于您的应用程序,这可能就足够了,有很多完全实现的示例,并且个人更容易实现。
回答by alexbk66
The winner is: Capture Keyboard Input in WPF, which suggests doing :
获胜者是:在 WPF 中捕获键盘输入,建议执行以下操作:
TextCompositionManager.AddTextInputHandler(this,
new TextCompositionEventHandler(OnTextComposition));
...and then simply use the event handler argument's Text property:
...然后只需使用事件处理程序参数的 Text 属性:
private void OnTextComposition(object sender, TextCompositionEventArgs e)
{
string key = e.Text;
...
}
回答by Alen.Toma
I really was looking for this. Thank you for posting this here.
Now, when I tested your code I found a few bugs. The code did not work at first. And it could not handle two buttons click i.e.: CTRL+ P.
What I have changed are those values look below:private void HookCallbackInner
to
我真的在找这个。感谢您在此处发布此信息。
现在,当我测试您的代码时,我发现了一些错误。该代码起初不起作用。它无法处理两个按钮点击即: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();
}
}
}
this code work 100% in windows 10 for me :) I hope this help u
这段代码在 Windows 10 中 100% 为我工作:) 我希望这对你有帮助
回答by EvilInside
I have used the Dylan's methodto hook global keyword in WPF application and refresh hook after each key press to prevent events stop firing after few clicks . IDK, if it is good or bad practice but gets the job done.
我使用Dylan 的方法在 WPF 应用程序中挂钩全局关键字,并在每次按键后刷新挂钩,以防止事件在点击几下后停止触发。IDK,无论是好是坏,但都能完成工作。
_listener.UnHookKeyboard();
_listener.HookKeyboard();
Implementation details here
实现细节在这里