Using global keyboard (WH_KEYBOARD_LL) in WPF / C #

I stitched together the code that I found on the Internet, the WH_KEYBOARD_LL class:

Put the following code in some of your utils libs, let it be 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); } 

What I use like this:

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

The problem is that it stops working after pressing keys . No error occurs that is always the case, I just get nothing from the output after a while. When it stops working, I can not find a solid pattern.

The reproduction of this problem is quite simple, some keys hit like a crazy person, usually outside the window.

I suspect there is some kind of problem with the thread, did anyone understand how to do this?




I have already tried:

  1. Replacement return HookCallbackInner(nCode, wParam, lParam); with something simple.
  2. Replace it with an asynchronous call, trying to set Sleep 5000ms (etc.).

An asynchronous call did not help him work better; he always stops when the user holds one letter for a while.

+53
c # winapi wpf
Oct 28 '09 at 18:52
source share
5 answers

You create a callback inline delegate in a call to the SetHook method. This delegate will eventually receive the garbage collection because you are not referencing it anywhere. And once the delegate collects the garbage, you will not receive any more callbacks.

To avoid this, you need to save the link to the delegate until the hook is installed on it (until you call UnhookWindowsHookEx).

+14
Oct 30 '09 at 10:02
source share

Winner: Capture keyboard input in WPF , which offers to do:

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

... and then just use the event handler arguments. Text Property:

 private void OnTextComposition(object sender, TextCompositionEventArgs e) { string key = e.Text; ... } 
+2
May 30 '12 at 6:52
source share

IIRC, when using global interceptors, if your DLL does not return from the callback fast enough, you are removed from the callback chain.

So, if you say that it works a little, but if you type too quickly, it stops working, I can suggest just storing the keys in some place in the memory and reset the keys later. For example, you can check the source for some keyloggers, as they use the same technique.

Although this may not solve your problem directly, it should at least rule out one possibility.

Have you considered using GetAsyncKeyState instead of a global hook for recording keystrokes? It may be enough for your application, there are many fully implemented examples and it would be easier to implement.

+1
Oct 28 '09 at 19:07
source share

I really looked for it. Thanks for posting it here. Now that I have checked your code, I have found some errors. The code didn't work at first. And he could not handle two buttons: i. <. T30> + P.
What I changed are those values ​​that look below:
private void HookCallbackInner before

 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 works 100% on windows 10 for me :) I hope this help helps you

0
Sep 06 '15 at 21:41
source share

I used the Dylan method to bind a global keyword in a WPF application and an update hook after each keystroke, to prevent events from stopping after a few clicks. IDK if it is a good or bad practice, but it does its job.

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

Implementation details here

0
May 31 '17 at 19:11
source share



All Articles