none
SetWindowsHookEx works in Net 2 but not in Net 4

    Question

  • I am developing an application that will engage a bar code scanner and will need to be listening for input even when it is not the active application or from a separate thread from within the application.  I can get a handle to the hook when I set the project to compile for Net 2.  When I set my project to compile for Net 4 I cannot get a handle from the SetWindowsHookEx.  Same code, no changes at all.  While obviously I can get a working DLL out of this it really is buggin the ____ out of me.  Additionally, if one of my colegues needs to adjust this DLL in the future and does not know, they may compile it in Net 4.  I also have no idea what will be the effects if I reference this DLL in another project that is compiled in Net 4.  Lastly, I am worried that I may encounter more difficulties like this in the future if I cannot figure out what the difference is.  Does anyone know why this is or what the solution might be?

    Emory

     

    <code>

    Imports System.Reflection
    Imports System.Runtime.InteropServices
    Imports System.Threading
    Imports System.Windows.Forms

    Public Class KeyboardKeyEventListener
        Implements IDisposable

    #Region " Members "

        Private Enum HookType As Integer
             WH_JOURNALRECORD = 0
             WH_JOURNALPLAYBACK = 1
             WH_KEYBOARD = 2
             WH_GETMESSAGE = 3
             WH_CALLWNDPROC = 4
             WH_CBT = 5
             WH_SYSMSGFILTER = 6
             WH_MOUSE = 7
             WH_HARDWARE = 8
             WH_DEBUG = 9
             WH_SHELL = 10
             WH_FOREGROUNDIDLE = 11
             WH_CALLWNDPROCRET = 12
             WH_KEYBOARD_LL = 13
             WH_MOUSE_LL = 14
        End Enum

        '// The Code Indicating Low Level Keyboard Message
        Private Const WH_KEYBOARD_LL As Integer = 13&

        '// Low-Level Keyboard Constants
        Private Const HC_ACTION As Integer = 0
        Private Const LLKHF_EXTENDED As Integer = &H1
        Private Const LLKHF_INJECTED As Integer = &H10
        Private Const LLKHF_ALTDOWN As Integer = &H20
        Private Const LLKHF_UP As Integer = &H80

        '// KeyUp/KeyDown Constants
        Private Const WM_KEYDOWN As Integer = &H100
        Private Const WM_KEYUP As Integer = &H101
        Private Const WM_SYSKEYDOWN As Integer = &H104
        Private Const WM_SYSKEYUP As Integer = &H105

        '// Variables
        Private KeyboardHandle As Integer
        Private Disposed As Boolean = False

        '// MarshalAs required to keep GC from recovering our delegate and creating a null reference when Windows sends the key event.
        <MarshalAs(UnmanagedType.FunctionPtr)> Private KeyboardHookProcedure As KeyboardHookDelegate = New KeyboardHookDelegate(AddressOf ProcessKeyboardMessage)

    #End Region

    #Region " Events "

        Public Event KeyDown(ByRef TheKeyArgs As Windows.Forms.KeyEventArgs)
        Public Event KeyPress(ByRef TheKeyArgs As Windows.Forms.KeyEventArgs)
        Public Event KeyUp(ByRef TheKeyArgs As Windows.Forms.KeyEventArgs)

    #End Region

    #Region " Delegates "

        Private Delegate Function KeyboardHookDelegate(ByVal Code As Integer, _
                                                       ByVal WParam As Integer, _
                                                       ByRef LParam As KBDLLHOOKSTRUCT) As Integer

    #End Region

    #Region " Function Declarations for 'user32.dll' "

        Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Integer) As Integer

        Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Integer, _
                                                                                          ByVal lpfn As KeyboardHookDelegate, _
                                                                                          ByVal hmod As Integer, _
                                                                                          ByVal dwThreadId As Integer) As Integer

        Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Integer) As Integer

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

    #End Region

    #Region " Structures "

        Private Structure KBDLLHOOKSTRUCT
            Public vkCode As Integer
            Public scanCode As Integer
            Public flags As Integer
            Public time As Integer
            Public dwExtraInfo As Integer
        End Structure

    #End Region

    #Region " Constructors, Destructors and Load Methods "

        Public Sub New()

            Try

            Catch ex As Exception
                Throw ex
            End Try

        End Sub

        Public Overridable Sub Dispose() Implements IDisposable.Dispose

            Try
                If Not (Disposed = True) Then
                    Disposed = True
                    If Not (KeyboardHandle = 0) Then UnhookWindowsHookEx(KeyboardHandle)
                Else
                    Throw New ObjectDisposedException("KeyboardHook")
                End If

            Catch ex As Exception
                Throw ex
            End Try

        End Sub

        Protected Overrides Sub Finalize()

            Try
                MyBase.Finalize()

                If Not Disposed Then
                    Dispose()
                End If

            Catch ex As Exception
                Throw ex
            End Try

        End Sub

    #End Region

    #Region " Methods - Primary "

        Public Function StartListening() As Boolean

            Try
                '// The first argument to SetWindowsHookEx Sets the Type of Hook.
                '// The second argument is the tagged delegate that contains the address of our local callback method.
                '// The third argument is the handle (hWnd) of the application doing the hooking
                '// The fourth argument is the thread to listen on.  Passing 0 indicates all threads.

                Console.WriteLine("KeyboardHandle = " + KeyboardHandle.ToString)
                Console.WriteLine("WH_KEYBOARD_LL = " + WH_KEYBOARD_LL.ToString)
                Console.WriteLine("KeyboardHookProcedure = " + KeyboardHookProcedure.ToString)
                Console.WriteLine("Current Assembly Thread = " + Marshal.GetHINSTANCE([Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32.ToString)
                Console.WriteLine("")

                KeyboardHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _
                                                  KeyboardHookProcedure, _
                                                  Marshal.GetHINSTANCE([Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, _
                                                  0)

                Console.WriteLine("KeyboardHandle = " + KeyboardHandle.ToString)

                If (KeyboardHandle = 0) Then
                    Return False
                Else
                    Return True
                End If

            Catch ex As Exception
                Throw New Exception("Exception encountered in StartListening" + vbCrLf + ex.Message)
            End Try

        End Function

        Public Function StopListening() As Boolean

            Try
                If Not (KeyboardHandle = 0) Then UnhookWindowsHookEx(KeyboardHandle)

                Console.WriteLine(KeyboardHandle.ToString)
                KeyboardHandle = 0

                Return True

            Catch ex As Exception
                Throw ex
            End Try

        End Function

        Private Function ProcessKeyboardMessage(ByVal Code As Integer, ByVal WParam As Integer, ByRef LParam As KBDLLHOOKSTRUCT) As Integer

            Try
                Dim TheKeys As Windows.Forms.Keys = CType(LParam.vkCode, Keys)
                Dim TheKeyArgs As Windows.Forms.KeyEventArgs = New Windows.Forms.KeyEventArgs(TheKeys)

                If WParam = WM_KEYDOWN Or WParam = WM_SYSKEYDOWN Then
                    RaiseEvent KeyDown(TheKeyArgs)
                ElseIf WParam = WM_KEYUP Or WParam = WM_SYSKEYUP Then
                    RaiseEvent KeyUp(TheKeyArgs)
                End If

                '// Call the next hook in the hook chain and return the value
                Return CallNextHookEx(KeyboardHandle, Code, WParam, LParam)

            Catch ex As Exception
                Throw New Exception("Exception encountered in ProcessKeyStroke" + vbCrLf + vbCrLf + ex.Message)
            End Try

        End Function

    #End Region

    #Region " Methods - Utility "

    #End Region

    End Class

    </code>

     

    • Edited by EmoryH Monday, May 24, 2010 10:20 PM Include code
    Monday, May 24, 2010 10:16 PM

All replies

  • Having same issue in .Net 4.0 call failing. Any suggestions? Pointers?

    Thanks in advance.


    noorbakhsh حميد نوربخش
    Tuesday, June 15, 2010 9:52 PM
  • If it fails in 4.0 which goes exactly in the same way as in 2.0 and that is sure, then it is probably a bug.

    http://connect.microsoft.com

    That is the website for bugs.

     


    Success
    Cor
    Wednesday, June 16, 2010 5:31 AM
  • Cor,

    Thanks for your reply. I changed my code and now it works. I noticed the following :

    hMod [in]
    HINSTANCE

    A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.

    Notice NULL setting for running this from the same process -> So in my call I now pass a
    IntPtr.Zero  (In my .Net 4.0 code)

    instead of:

    Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0])   (This is the parameter passed in my .Net 2.0 code)

    and it works since the thread is created by the current process. I am not sure if this was like this before or not, but now it is working.

     

    Edit: Above works great in Windows 7, but not in Windows XP, setting it to System.Diagnostics.Process.GetCurrentProcess().MainModule.BaseAddress will work in both (thanks to Patrick Klug for suggesting this)

     


    noorbakhsh حميد نوربخش
    • Proposed as answer by noorbakhsh Tuesday, October 12, 2010 3:15 PM
    Wednesday, June 16, 2010 3:02 PM
  • Thanks for the edit to get it to work in both XP and Win 7.

    Had issues with this in the ribbon control : http://ribbon.codeplex.com/

    Wednesday, September 22, 2010 11:56 PM

  • Thank you noorbakhsh, your post helped me a lot!
    Thursday, April 04, 2013 1:54 PM