title: Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C#
body: <p>I stitched together from code I found in internet myself <code>WH_KEYBOARD_LL</code> helper class:</p>
  
  <p>Put the following code to some of your utils libs, let it be <strong>YourUtils.cs</strong>:</p>
  
  <pre><code>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 &gt;= 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);
  }
  </code></pre>
  
  <p>Which I use like this:</p>
  
  <p><strong>App.xaml</strong>:</p>
  
  <pre><code>&lt;Application ...
      Startup="Application_Startup"
      Exit="Application_Exit"&gt;
      ...
  </code></pre>
  
  <p><strong>App.xaml.cs</strong>:</p>
  
  <pre><code>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();
      }
  }
  </code></pre>
  
  <p>The problem is that it <strong>stops working after hitting keys a while</strong>. 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.</p>
  
  <p>Reproducing this problem is quiet simple, hit some keys like a mad man, usually outside the window.</p>
  
  <p>I suspect there is some evil <em>threading problem</em> behind, anyone got idea how to keep this working?</p>
  
  <hr>
  
  <p>What I tried already:</p>
  
  <ol>
  <li>Replacing <code>return HookCallbackInner(nCode, wParam, lParam);</code> with something simple.</li>
  <li>Replacing it with asynchronous call, trying to put Sleep 5000ms (etc).</li>
  </ol>
  
  <p>Asynchronous call didn't make it work any better, it seems stop always when user keeps single letter down for a while.</p>