Utilizzo del hook della tastiera globale (WH_KEYBOARD_LL) in WPF / C #

Ho cucito insieme dal codice che ho trovato in internet WH_KEYBOARD_LL helper class:

Metti il ​​seguente codice ad alcune delle tue utils libs, lascia che sia 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); } 

Che uso in questo modo:

App.xaml :

  ... 

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

Il problema è che smette di funzionare dopo aver premuto un paio di tasti . Non viene sollevato alcun errore, quindi, non ottengo nulla da produrre dopo un po ‘. Non riesco a trovare uno schema solido quando smette di funzionare.

Riprodurre questo problema è semplice, colpire alcune chiavi come un pazzo, di solito fuori dalla finestra.

Sospetto che dietro ci sia qualche problema di threading malvagio, qualcuno ha idea di come mantenere questo funzionamento?


Quello che ho provato già:

  1. Sostituire return HookCallbackInner(nCode, wParam, lParam); con qualcosa di semplice
  2. Sostituendolo con una chiamata asincrona, cercando di mettere Sleep 5000ms (ecc.).

La chiamata asincrona non ha funzionato meglio, sembra fermarsi sempre quando l’utente mantiene una singola lettera per un po ‘.

Stai creando il tuo delegato di callback in linea nella chiamata al metodo SetHook. Quel delegato alla fine otterrà la raccolta dei rifiuti, dal momento che non tieni alcun riferimento ad esso ovunque. E una volta che il delegato è garbage collection, non riceverai più callback.

Per evitare ciò, è necessario mantenere un riferimento al delegato attivo fino a quando l’hook è a posto (fino a quando non si chiama UnhookWindowsHookEx).

Il vincitore è: Capture Keyboard Input in WPF , che suggerisce di fare:

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

… e quindi usa semplicemente la proprietà Text dell’argomento gestore di eventi:

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

IIRC, quando si usano i hook globali, se la tua DLL non ritorna abbastanza veloce dal callback, sei rimosso dalla catena di call-backs.

Quindi se stai dicendo che funziona per un po ‘, ma se digiti troppo velocemente, smetterà di funzionare, potrei suggerire di archiviare le chiavi in ​​qualche punto della memoria e di scaricare le chiavi in ​​un secondo momento. Per un esempio, è ansible controllare l’origine di alcuni keylogger poiché utilizzano la stessa tecnica.

Anche se questo potrebbe non risolvere il tuo problema direttamente, dovrebbe almeno escludere una possibilità.

Hai pensato di utilizzare GetAsyncKeyState invece di un hook globale per registrare le sequenze di tasti? Per la tua applicazione, potrebbe essere sufficiente, ci sono molti esempi pienamente implementati ed è stato personalmente più facile da implementare.

Lo stavo davvero cercando. Grazie per aver postato questo qui.
Ora, quando ho testato il tuo codice ho trovato alcuni bug. Il codice non ha funzionato all’inizio. E non poteva gestire due pulsanti, ad esempio: CTRL + P.
Quello che ho cambiato sono quei valori sotto:
private void HookCallbackInner a

 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 { ///  /// Interaction logic for App.xaml ///  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 _keysPressedIntowSecound = new List(); 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(); }; 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(); } } } 

questo codice funziona al 100% in Windows 10 per me 🙂 Spero che questo ti aiuti

Ho usato il metodo di Dylan per agganciare la parola chiave globale nell’applicazione WPF e aggiornare il hook dopo ogni pressione di un tasto per impedire che gli eventi cessino di triggersrsi dopo pochi clic. IDK, se è buona o ctriggers pratica, ma ottiene il lavoro fatto.

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

Dettagli di implementazione qui