Click here to Skip to main content
15,880,392 members
Articles / Programming Languages / C#
Article

A Simple C# Global Low Level Keyboard Hook

Rate me:
Please Sign up or sign in to vote.
4.90/5 (98 votes)
30 May 2007CPOL2 min read 971K   68.2K   171   187
A simple description and sample of creating a global low level keyboard hook in C#
Screenshot - key_preview.jpg

Introduction

This article discusses a class that I wrote that wraps a global low level keyboard hook. The sample hooks the A and B keys, just for demonstration.

Background

I was trying to find a way for an application that I am writing to restore itself when a combination of keys was pressed. This was born from searching around for the answer.

Using the Code

First download the source, and add globalKeyboardHook.cs to your project. Then add...

C#
using Utilities;

... to the top of the file you are going to use it in. Next add an instance of globalKeyboardHook to your class:

C#
globalKeyboardHook gkh = new globalKeyboardHook() ;

When a globalKeyboardHook is constructed, it automatically installs the hook, so all there is left to do is add some keys for it to watch, and define some event handlers for the KeyDown and KeyUp events. I usually do this on the main form's load event handler, like this:

C#
private void Form1_Load(object sender, EventArgs e) {
    gkh.HookedKeys.Add(Keys.A);
    gkh.HookedKeys.Add(Keys.B);
    gkh.KeyDown += new KeyEventHandler(gkh_KeyDown);
    gkh.KeyUp += new KeyEventHandler(gkh_KeyUp);
} 

void gkh_KeyUp(object sender, KeyEventArgs e) {
    lstLog.Items.Add("Up\t" + e.KeyCode.ToString());
    e.Handled = true ;
}

void gkh_KeyDown(object sender, KeyEventArgs e) {
    lstLog.Items.Add("Down\t" + e.KeyCode.ToString());
    e.Handled = true ;
} 

Here I have chosen to watch for the A and B keys, and defined handlers for the KeyUp and KeyDown events that both log to a listbox called lstLog. So whenever the user presses the A or B keys, no matter what has focus, the application will be notified. Setting e.Handled to true makes it so no other notifications for this event go out, in the sample, this effectively stops the user from typing an A or B. This can be useful in ensuring that key combinations are not also typed out when used.

You can add hooks for as many keys as you would like, just add them like above. Don't get frustrated if you add a hook for a key and it doesn't work, many of them, like Keys.Shift show up as other more specific keys, like Keys.LShiftKey or Keys.RShiftKey. Keys.Alt shows up as Keys.LMenu or Keys.RMenu, Keys.Control shows up as Keys.LControl or Keys.RControl, just to name a few.

If you would like to hook or unhook the keyboard hook at any point, just call your globalKeyboardHook's hook and unhook methods, like so:

C#
//unhook
gkh.unhook() 
//set the hook again
gkh.hook() 

Points of Interest

The bulk of the work in this code is done in the globalKeyboardHook class, although it is a fairly simple piece of code itself. The hardest part of doing this was finding the correct parameters for SetWindowsHookEx.

C#
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx
    (int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);

The parameters were worked out to be:

C#
IntPtr hInstance = LoadLibrary("User32");
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);

The first parameter WH_KEYBOARD_LL is just saying that we want to hook the low level keyboard events, hookProc is the callback for the event, hInstance is a handle to User32.dll, where this event is first processed (I think). The last parameter is if you want to hook a specific thread, then you would just pass a thread id instead of using the hInstance.

History

  • 5/30/07 - First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
I currently work as a Software Engineer for a company in North Carolina, mainly working with C#.

Comments and Discussions

 
GeneralAwesome! Pin
KhoiTran197-Dec-10 17:45
KhoiTran197-Dec-10 17:45 
GeneralMy vote of 5 Pin
mesuhas_sit7-Dec-10 16:52
mesuhas_sit7-Dec-10 16:52 
Generalctrl+alt+i Pin
pukino18030-Sep-10 10:13
pukino18030-Sep-10 10:13 
GeneralRe: ctrl+alt+i Pin
DungVinh12-Feb-12 23:48
DungVinh12-Feb-12 23:48 
QuestionForeign characters? Pin
lvq68411-Sep-10 2:36
lvq68411-Sep-10 2:36 
GeneralAll keys Pin
Member 724777630-Jul-10 5:35
Member 724777630-Jul-10 5:35 
GeneralRe: All keys Pin
jrbosch2-Jan-11 21:47
jrbosch2-Jan-11 21:47 
GeneralMy vote of 4 Pin
angelamerkel28-Jun-10 10:11
angelamerkel28-Jun-10 10:11 
GeneralMy vote of 1 Pin
Muhammad Waleed Ashraf18-Jun-10 1:38
Muhammad Waleed Ashraf18-Jun-10 1:38 
QuestionKey combination like CTRL+ALT+A? Pin
uhcafigdc4-Jun-10 9:03
uhcafigdc4-Jun-10 9:03 
General[My vote of 2] Sorry not perfect... Pin
johannesnestler25-May-10 23:50
johannesnestler25-May-10 23:50 
Have a look at your marshalling! (Attributes) Your code can lead to some (seldom but nasty) errors.
So sorry for the bad vote but your code seems to be reused by a lot of people, i will revote if you improve your code.
A good site is: http://www.pinvoke.net[^]
Maybe you are also interested in the way I get the Instance handle for the process containing the callback.

Have a look at the code I use for a keyboard hook:

/* File: GlobalKeyboardHook.cs
 * Proj: Common
 * Date: 20.03.2009
 * Desc: GlobalKeyboardHook - Helper class for global (system-wide) keyboard hooks. 
 * Elem: CLASS GlobalKeyboardHook - -"-
 * Auth: © Johannes Nestler 2009 */


using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;


namespace Common
{
    #region CLASS GlobalKeyboardHook

    /// <summary>
    /// Helper class for global (system-wide) keyboard hooks.
    /// </summary>        
    public class GlobalKeyboardHook
    {
        #region TYPES

        #region CLASS KeyboardHookStruct

        /// <summary>
        /// Marshalling of the Windows-API KBDLLHOOKSTRUCT structure.#
        /// Contains information about a low-level keyboard input event.
        /// This is named "struct" to be consistent with the Windows API name,
        /// but it must be a class since it is passed as a pointer in SetWindowsHookEx.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        class KeyboardHookStruct
        {
            public int vkCode;
            public int scanCode;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }

        #endregion // CLASS KeyboardHookStruct

        #region DELEGATE HookProc

        /// <summary>
        /// Represents the method called when a hook catches a monitored event.
        protected delegate int HookProc(int nCode, int wParam, IntPtr lParam);

        #endregion // DELEGATE HookProc

        #endregion // TYPES

        #region CONSTANTS

        const int WH_KEYBOARD_LL = 13;
        const int WH_KEYBOARD = 2;

        const int WM_KEYDOWN = 0x100;
        const int WM_KEYUP = 0x101;
        const int WM_SYSKEYDOWN = 0x104;
        const int WM_SYSKEYUP = 0x105;

        const byte VK_SHIFT = 0x10;
        const byte VK_CAPITAL = 0x14;
        const byte VK_NUMLOCK = 0x90;

        const byte VK_LSHIFT = 0xA0;
        const byte VK_RSHIFT = 0xA1;
        const byte VK_LCONTROL = 0xA2;
        const byte VK_RCONTROL = 0x3;
        const byte VK_LALT = 0xA4;
        const byte VK_RALT = 0xA5;

        // const byte LLKHF_ALTDOWN = 0x20; // not used

        #endregion // CONSTANTS

        #region VARIABLES

        /// <summary>
        /// Value indicating if hook is active.
        /// </summary>
        bool m_bHookActive;

        /// <summary>
        /// Stored hook handle returned by SetWindowsHookEx
        /// </summary>
        int m_iHandleToHook;

        /// <summary>
        /// Stored reference to the HookProc delegate (to prevent delegate from beeing collected by GC!)
        /// </summary>
        protected HookProc m_hookproc;

        #endregion // VARIABLES

        #region EVENTS

        /// <summary>
        /// Occurs when a key is pressed.
        /// </summary>
        public event KeyEventHandler KeyDown;
        /// <summary>
        /// Occurs when a key is released.
        /// </summary>
        public event KeyEventHandler KeyUp;
        /// <summary>
        /// Occurs when a character key is pressed.
        /// </summary>
        public event KeyPressEventHandler KeyPress;

        #endregion // EVENTS

        #region CONSTRUCTION & DESTRUCTION

        /// <summary>
        /// Dtor.
        /// </summary>
        ~GlobalKeyboardHook()
        {
            Unhook();
        }

        #endregion // CONSTRUCTION & DESTRUCTION

        #region PROPERTIES

        /// <summary>
        /// Gets a value indicating if hook is active.
        /// </summary>
        public bool HookActive
        {
            get { return m_bHookActive; }
        }

        #endregion // PROPERTIES

        #region METHODS

        /// <summary>
        /// Install the global hook. 
        /// </summary>
        /// <returns> True if hook was successful, otherwise false. </returns>
        public bool Hook()
        {
            if(!m_bHookActive)
            {
                m_hookproc = new HookProc(HookCallbackProcedure);

                IntPtr hInstance = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
                m_iHandleToHook = SetWindowsHookEx(
                    WH_KEYBOARD_LL,
                    m_hookproc,
                    hInstance,
                    0);                

                if(m_iHandleToHook != 0)
                {
                    m_bHookActive = true;
                }
            }
            return m_bHookActive;
        }

        /// <summary>
        /// Uninstall the global hook.
        /// </summary>
        public void Unhook()
        {
            if(m_bHookActive)
            {
                UnhookWindowsHookEx(m_iHandleToHook);
                m_bHookActive = false;
            }
        }

        /// <summary>
        /// Raises the KeyDown event.
        /// </summary>
        /// <param name="kea"> KeyEventArgs </param>
        protected virtual void OnKeyDown(KeyEventArgs kea)
        {
            if(KeyDown != null)
                KeyDown(this, kea);
        }

        /// <summary>
        /// Raises the KeyUp event.
        /// </summary>
        /// <param name="kea"> KeyEventArgs </param>
        protected virtual void OnKeyUp(KeyEventArgs kea)
        {
            if(KeyUp != null)
                KeyUp(this, kea);
        }

        /// <summary>
        /// Raises the KeyPress event.
        /// </summary>
        /// <param name="kea"> KeyEventArgs </param>
        protected virtual void OnKeyPress(KeyPressEventArgs kpea)
        {
            if(KeyPress != null)
                KeyPress(this, kpea);
        }

        #endregion // METHODS

        #region EVENTHANDLER

        /// <summary>
        /// Called when hook is active and a key was pressed.
        /// </summary>
        int HookCallbackProcedure(int nCode, int wParam, IntPtr lParam)
        {
            bool bHandled = false;

            if(nCode > -1 && (KeyDown != null || KeyUp != null || KeyPress != null))
            {
                // Get keyboard data
                KeyboardHookStruct khs = (KeyboardHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

                // Get key states
                bool bControl = ((GetKeyState(VK_LCONTROL) & 0x80) != 0) || ((GetKeyState(VK_RCONTROL) & 0x80) != 0);
                bool bShift = ((GetKeyState(VK_LSHIFT) & 0x80) != 0) || ((GetKeyState(VK_RSHIFT) & 0x80) != 0);
                bool bAlt = ((GetKeyState(VK_LALT) & 0x80) != 0) || ((GetKeyState(VK_RALT) & 0x80) != 0);
                bool bCapslock = (GetKeyState(VK_CAPITAL) != 0);

                // Create KeyEventArgs 
                KeyEventArgs kea = new KeyEventArgs((Keys) (khs.vkCode |
                        (bControl ? (int) Keys.Control : 0) |
                        (bShift ? (int) Keys.Shift : 0) |
                        (bAlt ? (int) Keys.Alt : 0)));

                // Raise KeyDown/KeyUp events
                if(wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
                {
                    OnKeyDown(kea);
                    bHandled = kea.Handled;
                }
                else if(wParam == WM_KEYUP || wParam == WM_SYSKEYUP)
                {
                    OnKeyUp(kea);
                    bHandled = kea.Handled;
                }

                // Raise KeyPress event
                if(wParam == WM_KEYDOWN && !bHandled && !kea.SuppressKeyPress)
                {
                    byte[] abyKeyState = new byte[256];
                    byte[] abyInBuffer = new byte[2];
                    GetKeyboardState(abyKeyState);

                    if(ToAscii(khs.vkCode, khs.scanCode, abyKeyState, abyInBuffer, khs.flags) == 1)
                    {
                        char chKey = (char) abyInBuffer[0];
                        if((bCapslock ^ bShift) && Char.IsLetter(chKey))
                            chKey = Char.ToUpper(chKey);
                        KeyPressEventArgs kpea = new KeyPressEventArgs(chKey);
                        OnKeyPress(kpea);
                        bHandled = kea.Handled;
                    }
                }
            }

            if(bHandled)
                return 1;
            else
                return CallNextHookEx(m_iHandleToHook, nCode, wParam, lParam);
        }

        #endregion // EVENTHANDLER

        #region EXTERN

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

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        static extern int UnhookWindowsHookEx(int idHook);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        static extern int GetKeyboardState(byte[] pbKeyState);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        static extern short GetKeyState(int vKey);

        [DllImport("user32.dll")]
        static extern int ToAscii(int uVirtKey, int uScanCode, byte[] lpbKeyState, byte[] lpwTransKey, int fuState);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);

        #endregion // EXTERN
    }

    #endregion // CLASS GlobalKeyboardHook
}


btw: This is .NET not Java - the convention is: type names start with capital letters...
GeneralRe: [My vote of 2] Sorry not perfect... Pin
VHong99923-Sep-11 16:45
VHong99923-Sep-11 16:45 
GeneralI get an error.... Pin
moni9425-May-10 0:42
moni9425-May-10 0:42 
GeneralRe: I get an error.... Pin
mahboobeh mohamadi31-Jan-11 20:08
mahboobeh mohamadi31-Jan-11 20:08 
GeneralStrange problem concernign Arrow Keys [modified] Pin
muff997-Apr-10 1:28
muff997-Apr-10 1:28 
GeneralOverride F1 Pin
Fullmetal9901229-Mar-10 8:24
Fullmetal9901229-Mar-10 8:24 
GeneralApp locks up Pin
Fullmetal9901228-Mar-10 10:41
Fullmetal9901228-Mar-10 10:41 
GeneralKeyboard cant type! Pin
BuggingMe22-Mar-10 10:57
BuggingMe22-Mar-10 10:57 
GeneralRe: Keyboard cant type! Pin
StormySpike23-Mar-10 14:10
StormySpike23-Mar-10 14:10 
GeneralEXCELLENT! [modified] Pin
alexis410-Dec-09 9:05
alexis410-Dec-09 9:05 
GeneralSystem.NullReferenceException Pin
MariusUt1-Dec-09 8:27
MariusUt1-Dec-09 8:27 
GeneralExample worked, but in my program the most bizzare error... Pin
Phill6425-Nov-09 2:11
Phill6425-Nov-09 2:11 
GeneralRe: Example worked, but in my program the most bizzare error... Pin
Phill6425-Nov-09 10:30
Phill6425-Nov-09 10:30 
GeneralCallback Error Pin
Voulnet29-Oct-09 5:38
Voulnet29-Oct-09 5:38 
GeneralRe: Callback Error Pin
Voulnet29-Oct-09 6:32
Voulnet29-Oct-09 6:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.