28
Mar
09

[VB.net] Keyboard Hook Class

The keyboard hook from my old blog; “Low Level Keyboard Hook (Global) – Installing a Low Level Keyboard Hook”
This version is slightly updated, to cast the vkCode to the .net Keys enum to make key handling easier.

[16 June 2011 complete rewrite – should now work on every system -_-]

Imports System.Runtime.InteropServices

Public Class KeyboardHook

    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Private Overloads Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal HookProc As KBDLLHookProc, ByVal hInstance As IntPtr, ByVal wParam As Integer) As Integer
    End Function
    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Private Overloads Shared Function CallNextHookEx(ByVal idHook As Integer, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
    End Function
    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Private Overloads Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Boolean
    End Function

    <StructLayout(LayoutKind.Sequential)> _
    Private Structure KBDLLHOOKSTRUCT
        Public vkCode As UInt32
        Public scanCode As UInt32
        Public flags As KBDLLHOOKSTRUCTFlags
        Public time As UInt32
        Public dwExtraInfo As UIntPtr
    End Structure

    <Flags()> _
    Private Enum KBDLLHOOKSTRUCTFlags As UInt32
        LLKHF_EXTENDED = &H1
        LLKHF_INJECTED = &H10
        LLKHF_ALTDOWN = &H20
        LLKHF_UP = &H80
    End Enum

    Public Shared Event KeyDown(ByVal Key As Keys)
    Public Shared Event KeyUp(ByVal Key As Keys)

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const HC_ACTION As Integer = 0
    Private Const WM_KEYDOWN = &H100
    Private Const WM_KEYUP = &H101
    Private Const WM_SYSKEYDOWN = &H104
    Private Const WM_SYSKEYUP = &H105

    Private Delegate Function KBDLLHookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer

    Private KBDLLHookProcDelegate As KBDLLHookProc = New KBDLLHookProc(AddressOf KeyboardProc)
    Private HHookID As IntPtr = IntPtr.Zero

    Private Function KeyboardProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
        If (nCode = HC_ACTION) Then
            Dim struct As KBDLLHOOKSTRUCT
            Select Case wParam
                Case WM_KEYDOWN, WM_SYSKEYDOWN
                    RaiseEvent KeyDown(CType(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode, Keys))
                Case WM_KEYUP, WM_SYSKEYUP
                    RaiseEvent KeyUp(CType(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode, Keys))
            End Select
        End If
        Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam)
    End Function

    Public Sub New()
        HHookID = SetWindowsHookEx(WH_KEYBOARD_LL, KBDLLHookProcDelegate, System.Runtime.InteropServices.Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32, 0)
        If HHookID = IntPtr.Zero Then
            Throw New Exception("Could not set keyboard hook")
        End If
    End Sub

    Protected Overrides Sub Finalize()
        If Not HHookID = IntPtr.Zero Then
            UnhookWindowsHookEx(HHookID)
        End If
        MyBase.Finalize()
    End Sub

End Class

Usage:
To create the hook

Private WithEvents kbHook As New KeyboardHook

Then each event can be handled:

Private Sub kbHook_KeyDown(ByVal Key As System.Windows.Forms.Keys) Handles kbHook.KeyDown Debug.WriteLine(Key.ToString) End Sub Private Sub kbHook_KeyUp(ByVal Key As System.Windows.Forms.Keys) Handles kbHook.KeyUp Debug.WriteLine(Key) End Sub

Note: To run this inside Visual Studio, you will need to go to:
Project -> [Project Name] Properties -> Debug -> Uncheck “Enable the Visual Studio hosting process”
As that intercepts the hooked messages before your program.


47 Responses to “[VB.net] Keyboard Hook Class”


  1. April 1, 2009 at 6:58 pm

    Hi! As always, a great guide.

    But now i have some problems, i can´t see the text it records. How do i do that?

  2. 2 ih4x
    April 1, 2009 at 7:03 pm

    When you handle to events, instead of doing Debug.WriteLine you could, say do Textbox1.text = Key.Tostring or similar

  3. 3 Someone
    September 7, 2009 at 2:01 am

    Hi, This is really amazing, thanks a million!

  4. 4 Mauri
    October 20, 2009 at 1:54 pm

    Thanks, it was very usefull!

  5. 5 djenn
    January 6, 2010 at 6:15 pm

    I’ve succesfuly used this code in a program,I stored the code in a module without a problem.
    But now I want to use the same module in a different program and have a problem.
    When my progam executes this line:

    KeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyHookDelegate, System.Runtime.InteropServices.Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

    The variable KeyHook remains zero.
    And i swear, it’s the same module I use whith succes in an other program.
    Does someone has a clue?

    Grtz, djenn

  6. 6 djenn
    January 6, 2010 at 9:18 pm

    I found the problem,
    In the project propertys go to the debug page and uncheck:
    ‘Enable the visual studio host process’
    and the job was done

    djenn

  7. 7 Laurence
    January 16, 2010 at 10:09 pm

    I tried using your code and it works fine except for tryig to catch special charcters such as the $!@ etc. I have a bar code scanner that i wish to tie into with your code but the Key.Tostring returns D4 for the 4 as well as returning the “$” symbol. Any suggestions?

  8. 9 Mark
    January 23, 2010 at 1:50 am

    Hi,

    First of all, I’d like to thank you for this “guide” into keyboard hooking. It’s a quality piece
    I encounter the following problem though: In Windows XP, I have no problem at all with the code, it works like a charm. In Windows 7, it doesn’t work at all, and the events do not get raised. It seems as if the keyboard isn’t hooked in the first place to be honest.

    Do you know what I should do? I’ll keep an eye out for the answer :-).
    Thanks in advance.

    Cheers!
    Mark

    • 10 sim0n
      January 23, 2010 at 2:07 pm

      Ive just done a quick search around, and it seems that the hooks should still work on Win7. I just tested some of my old projects that use hooks (Mouse + Keyboard) and both still seem to work fine on Windows 7, so I guess the problem must be on your end?

      • 11 Muhammad irfan
        June 26, 2010 at 2:09 pm

        It works fine but always returns capital key value. even my caps is off it returns capital character

      • 12 sim0n
        June 26, 2010 at 5:59 pm

        You will need to check the state of the caps lock key then use Char.ToLower(c) (or whatever) based on the state of it.

    • 13 djenn
      January 23, 2010 at 8:12 pm

      Have you tryed the solution I game on ?
      I had the same problem until I disabled the virtual hosting process on the debug tab
      djenn

  9. 14 Ahmed
    June 7, 2010 at 8:45 am

    thank u. It was mos useful.
    I want to know how to disable Lwin key & Rwin key tocafe software????

  10. 15 CodeCruiser
    June 17, 2010 at 5:30 pm

    A side question, how do you get that code window in the blog? I am searching for similar thing for my blog.

  11. 18 LordGlaceon
    February 6, 2011 at 11:50 pm

    This is incredibly useful
    Just had to improve one tiny thing:
    Added the ability to intercept the keys allowing me to make my calculator work without too the numbers phasing through to the application below.

  12. 19 suresh
    February 14, 2011 at 5:36 am

    i have problem on this code by using word document.

    An outgoing call cannot be made since the application is dispatching an input-synchronous call. (Exception from HRESULT: 0x8001010D (RPC_E_CANTCALLOUT_ININPUTSYNCCALL))”
    Source=”Interop.Word”

  13. 20 Ben
    March 10, 2011 at 6:01 am

    How would you check the state of the caps lock key? or shift key?

  14. 21 FunCoder
    May 1, 2011 at 4:46 am

    I tried the module, and although it does what it describes, it returns with an error each time:
    I am using vb studio 2010.

    “A call to PInvoke function ‘iChart!iChart.modHooks+KeyboardHook::CallNextHookEx’ has unbalanced
    the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature”

    Anyone know how to avoid this?
    Thanks,
    Funcoder

  15. 22 FunCoder
    May 1, 2011 at 4:48 am

    The aforementioned error occurs at the following line:
    Return CallNextHookEx(KeyHook, nCode, wParam, lParam)

    • 23 sim0n
      May 1, 2011 at 12:28 pm

      Maybe the function definition of

      Private Declare Function CallNextHookEx Lib “user32” _
      (ByVal hHook As Integer, _
      ByVal nCode As Integer, _
      ByVal wParam As Integer, _
      ByVal lParam As KBDLLHOOKSTRUCT) As Integer

      Should be:

      _
      Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As WindowsMessages, ByVal lParam As KBDLLHOOKSTRUCT) As IntPtr
      End Function

      Or the lParam as ByRef

  16. 24 SomeOne
    May 9, 2011 at 7:53 pm

    Thans for sharing!
    This was really usefull.

  17. 25 BrianS
    June 1, 2011 at 12:17 pm

    I’ve been getting the same error at the same spot. Windows 7 (VB 2010)
    So I replaced the aforementioned troubled function with the recommended:

    Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As WindowsMessages, ByVal lParam As KBDLLHOOKSTRUCT) As IntPtr
    End Function

    But the IDE gave the following error:
    Error 1 Type ‘WindowsMessages’ is not defined. C:\Users\SVSS\documents\visual studio 2010\Projects\testKeys\testKeys\Cls_KeyboardHook.vb 54 105 testKeys

    So I changed the function to the following and the IDE error dissapears:
    Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As Windows.Forms.Keys, ByVal lParam As KBDLLHOOKSTRUCT) As IntPtr
    End Function

    So my application seems to work but the IDE ‘Immediate Window’ loggs the following message on every keypress

    “A first chance exception of type ‘System.ArgumentOutOfRangeException’ occurred in mscorlib.dll”

    Any Ideas?

    Oh and I’m not able to find a way to catch the key.modifier to check if shift key is down. Don’t suppose you have an example how the modifier can be tracked. Currently I trap for key up/down events for the key code on the shift key but that seems to take a lot of coding for something so simple.

  18. 26 Nadia
    June 16, 2011 at 2:37 pm

    Hi, I used the class above exactly without any changes. It works perfect In Windows XP, I have no problem at all with the code. In Windows 7, it doesn’t work at all, and the events do not get raised. Thanks in advance.

    • 27 sim0n
      June 16, 2011 at 3:43 pm

      The problem is probably due to x86/x64 problems with the signatures of the user32 functions. They were all using Integers, however should be using IntPtrs in order to work on both systems. When I tried to run the original code I had posted here on my machine it unbalanced the stack due to incorrect signatures…

      Ive completley rewritten the class from scratch and updated it at the top, it should now work!

      • 28 Nadia
        June 16, 2011 at 4:04 pm

        Thank you very much for your quick reply. But now I have another problem which is HHookID is zero and the message is “Could not set keyboard hook”.

        from the form I have:
        Private WithEvents kbHook As New KeyboardHook
        then in the keyboardHook class

        Public Sub New()
        HHookID = SetWindowsHookEx(WH_KEYBOARD_LL, KBDLLHookProcDelegate, System.Runtime.InteropServices.Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32, 0)
        If HHookID = IntPtr.Zero Then
        Throw New Exception(“Could not set keyboard hook”)
        End If
        End Sub

        Thanks a lot.

  19. 29 Nadia
    June 16, 2011 at 5:55 pm

    Thank you very much for your quick reply. But now I have another problem which is HHookID is zero and the message is “Could not set keyboard hook”.from the form I have:Private WithEvents kbHook As New KeyboardHookthen in the keyboardHook classPublic Sub New()HHookID = SetWindowsHookEx(WH_KEYBOARD_LL, KBDLLHookProcDelegate, System.Runtime.InteropServices.Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32, 0)If HHookID = IntPtr.Zero ThenThrow New Exception(“Could not set keyboard hook”)End IfEnd SubThanks a lot.

  20. 30 Nadia
    June 16, 2011 at 5:57 pm

    Hi, I replaced your updated code, HHookID is zero. Do you have any suggestion?
    Thanks

  21. 31 Nadia
    June 16, 2011 at 6:31 pm

    Hi, I replaced your updated code, HHookID is zero. Do you have any suggestion?Thanks

  22. 32 nadia
    June 23, 2011 at 6:10 pm

    Finally my program is working perfect, thanks a lot for the help. I have one more question, how can I modify the code above by disable and able some keys, such as alt & tab, alt Esc, Lwin.

  23. 33 Bill
    July 21, 2011 at 2:43 pm

    If you guys want to check for caps lock or shift state here you go:

    If Control.IsKeyLocked(Keys.CapsLock) Or Control.ModifierKeys = Keys.Shift Then
    txtKey.Text = Key.ToString.ToUpper
    Else
    txtKey.Text = Key.ToString.ToLower
    End If

  24. September 22, 2011 at 1:13 am

    I am using your code (successfully) to capture input from my bar code scanner. I am looking for a certain string of characters coming from my bar code reader and then acting on the scan. But… If I know it is from my scanner, i want to do something with the scan and then set the input string to nothing so that if I have another application in focus it does not put my scan in that app.

    Example: I have my program in the background waiting for a scan, but a user is also typing a document in Word, someone scans a barcode, my app recognizes it as a scan, deals with it, but then Word puts the text into Word as well. How can I tell Windows to kill the scan message after i receive it so it does not go into word as well?

    Thanks in advance!

    • 35 sim0n
      February 15, 2012 at 9:44 am

      You would need to return 1 from the KeyboardProc for messages you dont want to be passed on, rather than CallNextHookEx if I remember correctly

  25. 36 Brigatti
    October 4, 2011 at 6:19 am

    sim0n, thanks A LOT for sharing this to us.
    I was planing to do a thing and didn’t find how to do that yet.
    I’ll explain the situation:
    I have a external usb numpad.
    I would like to change the entrances from this device to bind another key.
    For example, if I press “Numpad7” from the device the OS will receive the word “Test”.
    But I want to change just the entrance of my external Numpad, not of my standard keyboard. Do you know what I mean?
    On Control Painel -> Keyboard -> Hardware. I can see both keyboards (my standard and the external).
    Looks like the solution it’s simple, but I don’t know how to check from wich keyboard the key is typed.

  26. 39 Bob
    February 1, 2012 at 1:12 pm

    Simon,

    Let me start off by saying thank you… Great code!

    I am able to get it to work as-is with no problems when the class is located directly within my project.
    However, it fails if I use it as an external (VB.Net) dll, or if I simply include the class as part of an external project.
    BTW, I am using VisualStudio 2010 on Windows 7 Professional (64-bit).

    I have made sure to uncheck the “debug/hosting-process” thing (in the test app as well as the external dll and/or project), but that doesn’t help. No matter what I do, it keeps throwing the code-generated exception when it evaluates HHookID = IntPtr.Zero.

    Worse case scenario, I guess I could include it as part of my main project code, but that would cause a host of other issues that I would rather avoid if possible.

    Any help would be appreciated… hope you’re still out there and reading this (somewhat old) blog 🙂

    Bob

    • 40 sim0n
      February 15, 2012 at 9:39 am

      Hi, I am still “out here” reading the comments, however I havn’t looked at most of the code posted here in quite some time!
      If you still require help let me know and I’ll try writing an updated class that should hopefully work for you!

      I’ve seen a few other comments posted that say there are issues with it on Win7 x64, not sure if it is easily solvable or not but I can investigate if you need!

  27. 41 Frederick Volking
    February 15, 2012 at 12:31 am

    OUTSTANDING!
    Your code “Works-as-advertised”
    I’ve been looking for this snippet for 3 weeks!
    You’d be amazed how many snippets I’ve tried – and – THEY DON’T WORK!

    Thank you SO MUCH for an excellent, working, snippet!

  28. 42 Frederick Volking
    February 15, 2012 at 12:57 am

    Many Programmers think THEY CAN DO IT ALL. Not True. I’ve written code for over 35 years and I learned a long time ago (and I learned the hard way) that there are two basic types of programmers. “I” am a “Tool-User”. sim0n, you are a “Tool-Maker”.

    Basically speaking, Tool-User can build a house . . . because they how to use a hammer. But a Tool-User cannot architect a house, cannot plan out electrical wireing, cannot build a brick fireplace.

    A “Tool-User” relies on a “Tool-Maker” for those work pieces.

    Thanks sim0n for being a great TOOL-MAKER!
    Now I can take your work and build something.

  29. 43 MiniKahn
    August 6, 2013 at 8:07 pm

    Use
    HHookID = SetWindowsHookEx(WH_KEYBOARD_LL, KBDLLHookProcDelegate, 0, 0)
    To fix the problem with the IntPtr.Zero Problem.


Leave a reply to Ron Sell Cancel reply