Skip to main content
Email Password   helpLost your password?

Sample Image - CSLLKeyboard.jpg

Introduction

Cats and babies have a lot in common. They both like eating the house plants, and share the same hatred of closed doors. They also love using keyboards, with the result that the important email you were sending to your boss is dispatched in mid-sentence, your accounts in Excel are embellished with four rows of gobbledygook, and your failure to notice that Windows Explorer was open results in several files moving to the Recycle Bin.

The solution is an application which you can switch to as soon as the keyboard is under threat, and which will ensure that any keyboard activity is harmless. This article illustrates how the keyboard can be neutralized in a C# application using a low-level Windows API hook.

Background

There are a number of articles and code samples regarding hooks in Windows, and some of them are listed at the end of this article. Neutralizing the keyboard when children are around must be a common need -- someone here wrote almost exactly the same thing in C++[^]! However, when I was looking for source code to create my application, I found very few .NET examples, and none that involved a self-contained class in C#.

The .NET framework gives managed access to the keyboard events you'll need for most ordinary uses through KeyPress, KeyUp and KeyDown. Unfortunately, these events can't be used to stop Windows from processing key combinations like Alt+Tab or the Windows Start key, which allow users to navigate away from an application. The conveniently placed Windows key in particular was irresistible to my baby son!

The solution is to catch the keyboard events at the operating system level rather than through the framework. To do this, the application needs to use Windows API functions to add itself to the "hook chain" of applications listening for keyboard messages from the operating system. When it receives this type of message, the application can selectively pass the message on, as it normally should, or suppress it so that no further applications -- including Windows -- can act on it. This article explains how.

Please note, however, that this code only applies to NT-based versions of Windows (NT, 2000 and XP), and that it isn't possible to use this method to disable Ctrl+Alt+Delete (suggestions on how to do that can be found in this MSDN Magazine Q&A[^]).

Using the code

For ease of use, I have attached three separate zip files to this article. One contains only the KeyboardHook class which is the main focus of this article. The others are complete projects for an application called "Baby Keyboard Bash" which displays the keys' names or coloured shapes in response to keystrokes. For ease of use, I have included two projects, one for Microsoft Visual C# 2005 Express Edition and one for Visual Studio 2003.

Instantiating the class

The keyboard hook is set up and handled by the KeyboardHook class in keyboard.cs. This class implements IDisposable, so the simplest way to instantiate it is to use the using keyword in the application's Main() method, to enclose the Application.Run() call. This will ensure that the hook is set up as soon as the application starts and, more importantly, is disabled as the application shuts down.

The class raises an event to warn the application that a key has been pressed, so it is important for the main form to have access to the instance of KeyboardHook created in the Main() method; the simplest solution is to store this instance in a public member variable. In a Visual Studio 2003 project, this will usually go in the Form1 class (or whatever the application's main class is called), and in Visual Studio 2005, in the Program class in Program.cs.

KeyboardHook has three constructors to enable or disable certain settings:

Enabling Alt+Tab and/or the Windows keys allows the person who is actually using the computer to switch to another application and interact with it using the mouse while keystrokes continue to be trapped by the keyboard hook. The PassAllKeysToNextApp setting effectively disables the keystroke trapping; the class will still set up a low-level keyboard hook and raise its KeyIntercepted event, but it will also pass the keyboard events to other listening applications.

The simplest way to instantiate the class to trap all keystrokes is therefore:

public static KeyboardHook kh;

[STAThread]
static void Main()
{
  //Other code

  using (kh = new KeyboardHook())
  {
    Application.Run(new Form1());
  }

Handling the KeyIntercepted event

When a key is pressed, the KeyboardHook class raises a KeyIntercepted event containing some KeyboardHookEventArgs. This needs to be handled by a method of the type KeyboardHookEventHandler, which can be set up as follows:

kh.KeyIntercepted += new KeyboardHook.KeyboardHookEventHandler(kh_KeyIntercepted);

The KeyboardHookEventArgs returns the following information on the key that was pressed:

A method with the appropriate signature can then be used to perform whatever tasks the keystroke calls for. Here is an example from the enclosed sample application:

void kh_KeyIntercepted(KeyboardHookEventArgs e)
{
  //Check if this key event is being passed to

  //other applications and disable TopMost in 

  //case they need to come to the front

  if (e.PassThrough)
  {
    this.TopMost = false;
  }
  ds.Draw(e.KeyName);
}

The rest of this article explains how the low-level keyboard hook is implemented in KeyboardHook.

Implementing a low-level Windows API keyboard hook

The Windows API contains three methods in user32.dll that are useful for this purpose:

The key to creating an application which can hijack the keyboard is to implement the first two methods, and forgo the third. The result is that any keys pressed go no further than the application.

In order to achieve this, the first step is to include the System.Runtime.InteropServices namespace and import the API methods, starting with SetWindowsHookEx:

using System.Runtime.InteropServices
...
//Inside class:


[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
  LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

The code to import UnhookWindowsHookEx and CallNextHookEx is listed in the sections concerning those methods further in this article.

The next step is to call SetWindowsHookEx to set up the hook, passing the following four parameters:

SetWindowsHookEx returns a hook ID which will be used to unhook the application when it shuts down, so this needs to be stored in a member variable for future use. The relevant code from the KeyboardHook class is as follows:

private HookHandlerDelegate proc;
private IntPtr hookID = IntPtr.Zero;
private const int WH_KEYBOARD_LL = 13;


public KeyboardHook()
{
  proc = new HookHandlerDelegate(HookCallback);
  using (Process curProcess = Process.GetCurrentProcess())
  using (ProcessModule curModule = curProcess.MainModule)
  {
     hookID = SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                    GetModuleHandle(curModule.ModuleName), 0);
  }
}

Processing keyboard events

As mentioned above, SetWindowsHookEx requires a pointer to the callback function that will be used to process the keyboard events. It expects a function with the following signature:

LRESULT CALLBACK LowLevelKeyboardProc
(   int nCode,
    WPARAM wParam,
    LPARAM lParam
);

The C# method for setting up a "pointer to a function" is to use a delegate, so the first step in giving SetWindowsHookEx what it needs is to declare a delegate with the right signature:

    private delegate IntPtr HookHandlerDelegate(
        int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);

And then write a callback method with the same signature; this method will contain all the code that actually processes the keyboard event. In the case of KeyboardHook, it checks whether the keystroke should be passed to other applications and then raises the KeyIntercepted event. Here is a simplified version without the keystroke handling code:

private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYUP = 0x0105;

private IntPtr HookCallback(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam)
{
  //Filter wParam for KeyUp events only - otherwise this code

  //will execute twice for each keystroke (ie: on KeyDown and KeyUp)

  //WM_SYSKEYUP is necessary to trap Alt-key combinations

  if (nCode >= 0)
  { 
      if (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)
      {
        //Raise the event

        OnKeyIntercepted(new KeyboardHookEventArgs(lParam.vkCode, AllowKey));
      }
      //Return a dummy value to trap the keystroke

      return (System.IntPtr)1;
  }
  //The event wasn't handled, pass it to next application

  return CallNextHookEx(hookID, nCode, wParam, ref lParam);
}

A reference to HookCallback is then assigned to an instance of HookHandlerDelegate and passed in the call to SetWindowsHookEx, as illustrated in the previous section.

Whenever a keyboard event occurs, the following parameters will be passed to HookCallBack:

private struct KBDLLHOOKSTRUCT
{ 
  public int vkCode;
  int scanCode;
  public int flags;
  int time;
  int dwExtraInfo;
}

The two public parameters are the only ones used by the callback method in KeyboardHook. vkCoke returns the virtual key code, which can be cast to System.Windows.Forms.Keys to obtain the key's name, while flags indicates if this is an extended key (the Windows Start key, for instance) or if the Alt key was pressed at the same time. The complete code for the HookCallback method illustrates which flags values to check for in each case.

If the information provided by flags and the other components of the KBDLLHOOKSTRUCT are not needed, the signature of the callback method and delegate can be changed as follows:

private delegate IntPtr HookHandlerDelegate(
        int nCode, IntPtr wParam, IntPtr lParam);

In this case, lParam will return only the vkCode.

Passing keystrokes to the next application

A well-behaved keyboard hook callback method should end by calling the CallNextHookEx function and returning its result. This ensures that other applications get a chance to handle the keystrokes destined for them.

However, the key functionality of the KeyboardHook class is preventing keystrokes from being propagated to any further applications. So whenever it processes a keystroke, HookCallback returns a dummy value instead:

return (System.IntPtr)1;

On the other hand, it does call CallNextHookEx if it didn't handle the event, or if the parameter passed with KeyboardHook's overloaded constructor allows certain key combinations through.

CallNextHookEx is enabled by importing the function from user32.dll as shown in the following code:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
  IntPtr wParam, ref KeyInfoStruct lParam);

The imported method is then called by this line in the HookCallback method, which ensures that all the parameters received through the hook are passed on to the next application:

CallNextHookEx(hookID, nCode, wParam, ref lParam);

As mentioned before, if the flags in lParam are not relevant, the signature for the imported CallNextHookEx can be changed to define lParam as System.IntPtr.

Removing the hook

The last step in processing the hook is to remove it when the instance of the KeyboardHook class is destroyed, using the UnhookWindowsHookEx function imported from user32.dll as follows.

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

Since KeyboardHook implements IDisposable, this can be done in the Dispose method.

public void Dispose()
{
  UnhookWindowsHookEx(hookID);
}

hookID is the id returned by the call to SetWindowsHookEx in the constructor. This removes the application from the hook chain.

Sources

Here are some good sources on Windows hooks in C# and in general:

It seems that articles in any language seem to attract at least one response on the lines of "how do I do the same thing in (some other language)?", so here are some samples I found:

And nothing to do with Windows API hooks or C#, but I would like to recommend Mike Ellison's excellent Word 2003 template if you're ever planning to write an article for Code Project!

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralKeyboard events Pin
vinodachu
22:58 8 Oct '09  
GeneralWinForms Keys to WPF Keys Pin
Spacelord_XaN
0:48 29 Apr '09  
Generalthanks Pin
c870916
23:01 6 Apr '09  
QuestionHello Emma .... Its Doing so in ASP.NETWEBSITE? Pin
LuizItatiba
9:52 4 Mar '09  
QuestionHello Emma. need your help. Pin
dhaddo
9:26 2 Mar '09  
GeneralThanks Pin
flaunt
9:54 20 Dec '08  
GeneralNew WPF Version Available Pin
BarryDorman
9:53 26 Nov '08  
QuestionHelp! Demo works but seems to have a memory issue. Pin
cflux
18:29 11 Sep '08  
GeneralExcellent work Pin
jtradke
17:37 28 Aug '08  
GeneralEsc key acts as toggle? Pin
mikedrummo
0:27 7 Jul '08  
QuestionHelp! How come using this class as a Console App doesn't work? Pin
troy4u
23:13 1 Jul '08  
QuestionRe: Help! How come using this class as a Console App doesn't work? Pin
troy4u
0:11 2 Jul '08  
GeneralHow to neutralize keyboard for one form rather than the entire application? Pin
jonny_chympo
9:27 26 Jun '08  
QuestionHow to prevent keyboard's event raise? Pin
hoangnguyenletran
0:49 11 Mar '08  
QuestionWindows service Pin
leecha
23:59 6 Nov '07  
AnswerRe: Windows service Pin
Sandeep Aparajit
3:30 15 May '08  
GeneralA suggestion Pin
Mohamad K Ayash
5:09 5 Sep '07  
GeneralCtrl and Windows key Pin
digfreelancer
5:50 14 Aug '07  
GeneralProblems when hooking to the Esc key Pin
Zoltan Balazs
6:17 30 May '07  
GeneralNormally using the keystrokes in my app Pin
JohannesAckermann
5:58 24 May '07  
GeneralRe: Normally using the keystrokes in my app Pin
Emma Burrows
8:27 24 May '07  
GeneralRe: Normally using the keystrokes in my app Pin
JohannesAckermann
10:35 24 May '07  
QuestionCheckModifiers() incorrect on Vista Pin
DarkInGray
18:55 30 Apr '07  
QuestionHandling % character Pin
gkowaljow
11:05 11 Apr '07  
GeneralWindows Keyboard Lock Pin
Muhammed Sahin
23:28 27 Mar '07  


Last Updated 26 Mar 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009