locked
keyboard hook RRS feed

  • Question

  • I added a keyboard hook by calling the SetWindowsHookEx but the delegate I was passing as a parameter for the function was being garbage collected, although the delegate was declared as a global variable. I was instantiating the delegate in a method, but, after instantiating it in the constructor of the class that contained the delegate it stopped being garbage collected and the problem was solved.

    While doing some research I found Chris Brumme's blog were he Is saying that it's a better practice use a delegate and call the native method asynchronously rather than pinning the delegate that was GC in the memory. Therefore I tried this approach

    public delegate IntPtr SetHookDelegate(int idHook, HookProc lpfn, IntPtr hInstance, uint threadId);
    SetHookDelegate myookDelegate = null;


    using (Process curProcess = Process.GetCurrentProcess())
    using (ProcessModule curModule = curProcess.MainModule)
    {
    myookDelegate = new SetHookDelegate(Win32.SetWindowsHookEx);
    myookDelegate.BeginInvoke((int)Win32.HookType.WH_KEYBOARD_LL,
    HookProcedure,
    Win32.GetModuleHandle(curModule.ModuleName),
    0, new AsyncCallback(CallMeWhenFinished), myReturnValue);
    }

    but the callback method "CallMeWhenFinished" is called right away and the thread on which the hook was added is finished without waiting for the delegate passed as a parameter to the native function SetWindowsHookEx. Why is the thread finished right away and not listening on the callback function?
    Monday, April 21, 2008 10:08 AM

Answers

  • A couple of points. Firstly there is no such thing as a global variable in C#.  The best you can hope for (without IL interception) is a static member of a static class.  Generally you'll use Marshal.GetFunctionPointerForDelegate to get the IntPtr and then you'll cache the IntPtr in a static variable or use the GC to prevent it from being GC'ed.

     

    I currently use a simpler solution, in my opinion.  I have to manage approximately 50 callbacks from managed code.  I didn't want to create 50 different entry points in my unmanaged thunk layer to call these callbacks so I set up a simple enumeration that mapped to each callback.  I then exposed a single entry point on the unmanaged side to allow me to pass in the callback identifier along with the function pointer.  On the managed side I set up a dictionary that maps callback ids to the delegates.  This is stored as a static in the thunk class (because the callbacks are shared).  The nice thing about this mechanism is that when I start my cleanup I can enumerate the dictionary and notify the unmanaged code that the function pointer is no longer valid.  Ensures that I don't release the delegate before the unmanaged code is done with it.

     

    Here's some sample code:

     

    Code Snippet

    public static class Thunker

    {

       public static void Initialize ( )
       {

          SetCallback(Function1, OnFunction1);

          ...

       }

     

       private static void SetCallback ( int index, Delegate callback )

       {

         Unmanaged_SetCallback(index, Marshal.GetFunctionPointerForDelegate(callback));

     

         //Prevents the delegate from being collected

         m_Delegates[index] = callback;

       }

     

       private static Dictionary<CallbackId, Delegate> m_Delegates = ...;
    }

     

     

    Secondly, unless the window hook is local to your app then it is not a good idea to write managed window hooks.  This is especially true for hooks that reside in a DLL.  The problem is that in order for the managed DLL to be used the .NET framework has to be loaded into the unmanaged code's address space.  This can cause problems. 

     

    Michael Taylor - 4/21/08

    http://p3net.mvps.org

     

    Monday, April 21, 2008 1:14 PM
  •  Razvan Dimescu wrote:

    Indeed, the hook is not local to my app, I'll change my code and I'll move the hook to an umanaged dll

     

    The low level hooks (WH_KEYBOARD_LL and WH_MOUSE_LL) are exceptions to the rule Michael mentioned. They are always called in the process that installs the hook, so they can be implemented in managed code if you really want to.

    Tuesday, April 22, 2008 6:55 AM

All replies

  • I don't know what in Brumme's blog post made you write your code that way, but it seems unnecessary. SetWindowsHookEx is already an async function, so I don't see what you'd gain by using BeginInvoke.

     

     

    Monday, April 21, 2008 1:11 PM
  • A couple of points. Firstly there is no such thing as a global variable in C#.  The best you can hope for (without IL interception) is a static member of a static class.  Generally you'll use Marshal.GetFunctionPointerForDelegate to get the IntPtr and then you'll cache the IntPtr in a static variable or use the GC to prevent it from being GC'ed.

     

    I currently use a simpler solution, in my opinion.  I have to manage approximately 50 callbacks from managed code.  I didn't want to create 50 different entry points in my unmanaged thunk layer to call these callbacks so I set up a simple enumeration that mapped to each callback.  I then exposed a single entry point on the unmanaged side to allow me to pass in the callback identifier along with the function pointer.  On the managed side I set up a dictionary that maps callback ids to the delegates.  This is stored as a static in the thunk class (because the callbacks are shared).  The nice thing about this mechanism is that when I start my cleanup I can enumerate the dictionary and notify the unmanaged code that the function pointer is no longer valid.  Ensures that I don't release the delegate before the unmanaged code is done with it.

     

    Here's some sample code:

     

    Code Snippet

    public static class Thunker

    {

       public static void Initialize ( )
       {

          SetCallback(Function1, OnFunction1);

          ...

       }

     

       private static void SetCallback ( int index, Delegate callback )

       {

         Unmanaged_SetCallback(index, Marshal.GetFunctionPointerForDelegate(callback));

     

         //Prevents the delegate from being collected

         m_Delegates[index] = callback;

       }

     

       private static Dictionary<CallbackId, Delegate> m_Delegates = ...;
    }

     

     

    Secondly, unless the window hook is local to your app then it is not a good idea to write managed window hooks.  This is especially true for hooks that reside in a DLL.  The problem is that in order for the managed DLL to be used the .NET framework has to be loaded into the unmanaged code's address space.  This can cause problems. 

     

    Michael Taylor - 4/21/08

    http://p3net.mvps.org

     

    Monday, April 21, 2008 1:14 PM
  • Cool, thank you for your post.

    Indeed, the hook is not local to my app, I'll change my code and I'll move the hook to an umanaged dll
    Monday, April 21, 2008 2:42 PM
  •  Razvan Dimescu wrote:

    Indeed, the hook is not local to my app, I'll change my code and I'll move the hook to an umanaged dll

     

    The low level hooks (WH_KEYBOARD_LL and WH_MOUSE_LL) are exceptions to the rule Michael mentioned. They are always called in the process that installs the hook, so they can be implemented in managed code if you really want to.

    Tuesday, April 22, 2008 6:55 AM