相关文章推荐
直爽的柑橘  ·  IF...ELSE ...·  1 年前    · 
痴情的海龟  ·  html5制作 ...·  1 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I have been using this key hook script i found but I continue to get an error after a few seconds of using it in my program. The error says.. A call has been made on a garbage collected delegate 'keylogger!Utilities.globalKeyboardHook+keyboardHookProc::Invoke'.

How can I fix this?

namespace Utilities
/// <summary>
/// A class that manages a global low level keyboard hook
/// </summary>
class globalKeyboardHook
    #region Constant, Structure and Delegate Definitions
    /// <summary>
    /// defines the callback type for the hook
    /// </summary>
    public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);
    public struct keyboardHookStruct
        public int vkCode;
        public int scanCode;
        public int flags;
        public int time;
        public int dwExtraInfo;
    const int WH_KEYBOARD_LL = 13;
    const int WM_KEYDOWN = 0x100;
    const int WM_KEYUP = 0x101;
    const int WM_SYSKEYDOWN = 0x104;
    const int WM_SYSKEYUP = 0x105;
    #endregion
    #region Instance Variables
    /// <summary>
    /// The collections of keys to watch for
    /// </summary>
    public List<Keys> HookedKeys = new List<Keys>();
    /// <summary>
    /// Handle to the hook, need this to unhook and call the next hook
    /// </summary>
    IntPtr hhook = IntPtr.Zero;
    #endregion
    #region Events
    /// <summary>
    /// Occurs when one of the hooked keys is pressed
    /// </summary>
    public event KeyEventHandler KeyDown;
    /// <summary>
    /// Occurs when one of the hooked keys is released
    /// </summary>
    public event KeyEventHandler KeyUp;
    #endregion
    #region Constructors and Destructors
    /// <summary>
    /// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
    /// </summary>
    public globalKeyboardHook()
        hook();
    /// <summary>
    /// Releases unmanaged resources and performs other cleanup operations before the
    /// <see cref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
    /// </summary>
    ~globalKeyboardHook()
        unhook();
    #endregion
    #region Public Methods
    /// <summary>
    /// Installs the global hook
    /// </summary>
    public void hook()
        IntPtr hInstance = LoadLibrary("User32");
        hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
    /// <summary>
    /// Uninstalls the global hook
    /// </summary>
    public void unhook()
        UnhookWindowsHookEx(hhook);
    /// <summary>
    /// The callback for the keyboard hook
    /// </summary>
    /// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
    /// <param name="wParam">The event type</param>
    /// <param name="lParam">The keyhook event information</param>
    /// <returns></returns>
    public int hookProc(int code, int wParam, ref keyboardHookStruct lParam)
        if (code >= 0)
            Keys key = (Keys)lParam.vkCode;
            if (HookedKeys.Contains(key))
                KeyEventArgs kea = new KeyEventArgs(key);
                if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null))
                    KeyDown(this, kea);
                else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null))
                    KeyUp(this, kea);
                if (kea.Handled)
                    return 1;
        return CallNextHookEx(hhook, code, wParam, ref lParam);
    #endregion
    #region DLL imports
    /// <summary>
    /// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
    /// </summary>
    /// <param name="idHook">The id of the event you want to hook</param>
    /// <param name="callback">The callback.</param>
    /// <param name="hInstance">The handle you want to attach the event to, can be null</param>
    /// <param name="threadId">The thread you want to attach the event to, can be null</param>
    /// <returns>a handle to the desired hook</returns>
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);
    /// <summary>
    /// Unhooks the windows hook.
    /// </summary>
    /// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
    /// <returns>True if successful, false otherwise</returns>
    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);
    /// <summary>
    /// Calls the next hook.
    /// </summary>
    /// <param name="idHook">The hook id</param>
    /// <param name="nCode">The hook code</param>
    /// <param name="wParam">The wparam.</param>
    /// <param name="lParam">The lparam.</param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);
    /// <summary>
    /// Loads the library.
    /// </summary>
    /// <param name="lpFileName">Name of the library</param>
    /// <returns>A handle to the library</returns>
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);
    #endregion

globalKeyboardHook class :

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.IO;
namespace Utilities
/// <summary>
/// A class that manages a global low level keyboard hook
/// </summary>
class globalKeyboardHook : IDisposable
    private bool _disposed;
    #region Constant, Structure and Delegate Definitions
    /// <summary>
    /// defines the callback type for the hook
    /// </summary>
    public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);
    public struct keyboardHookStruct
        public int vkCode;
        public int scanCode;
        public int flags;
        public int time;
        public int dwExtraInfo;
    const int WH_KEYBOARD_LL = 13;
    const int WM_KEYDOWN = 0x100;
    const int WM_KEYUP = 0x101;
    const int WM_SYSKEYDOWN = 0x104;
    const int WM_SYSKEYUP = 0x105;
    #endregion
    #region Instance Variables
    /// <summary>
    /// The collections of keys to watch for
    /// </summary>
    public List<Keys> HookedKeys = new List<Keys>();
    /// <summary>
    /// Handle to the hook, need this to unhook and call the next hook
    /// </summary>
    IntPtr hhook = IntPtr.Zero;
    #endregion
    #region Events
    /// <summary>
    /// Occurs when one of the hooked keys is pressed
    /// </summary>
    public event KeyEventHandler KeyDown;
    /// <summary>
    /// Occurs when one of the hooked keys is released
    /// </summary>
    public event KeyEventHandler KeyUp;
    #endregion
    #region Constructors and Destructors
    /// <summary>
    /// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
    /// </summary>
    public globalKeyboardHook()
        hook();
        _disposed = false;
    public void Dispose()
        Dispose(true);
        // Use SupressFinalize in case a subclass
        // of this type implements a finalizer.
        GC.SuppressFinalize(this);
    protected virtual void Dispose(bool disposing)
        // If you need thread safety, use a lock around these 
        // operations, as well as in your methods that use the resource.
        if (!_disposed)
            if (disposing)
                unhook();
            // Indicate that the instance has been disposed.
            _disposed = true;
    /// <summary>
    /// Releases unmanaged resources and performs other cleanup operations before the
    /// <see cref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
    /// </summary>
    ~globalKeyboardHook()
        Dispose();
    #endregion
    #region Public Methods
    /// <summary>
    /// Installs the global hook
    /// </summary>
    public void hook()
        IntPtr hInstance = LoadLibrary("User32");
        hhook = SetWindowsHookEx(WH_KEYBOARD_LL, new keyboardHookProc(hookProc), hInstance, 0);
    /// <summary>
    /// Uninstalls the global hook
    /// </summary>
    public void unhook()
        UnhookWindowsHookEx(hhook);
    /// <summary>
    /// The callback for the keyboard hook
    /// </summary>
    /// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
    /// <param name="wParam">The event type</param>
    /// <param name="lParam">The keyhook event information</param>
    /// <returns></returns>
    public int hookProc(int code, int wParam, ref keyboardHookStruct lParam)
        if (code >= 0)
            Keys key = (Keys)lParam.vkCode;
            if (HookedKeys.Contains(key))
                KeyEventArgs kea = new KeyEventArgs(key);
                if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null))
                    KeyDown(this, kea);
                else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null))
                    KeyUp(this, kea);
                if (kea.Handled)
                    return 1;
        return CallNextHookEx(hhook, code, wParam, ref lParam);
    #endregion
    #region DLL imports
    /// <summary>
    /// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
    /// </summary>
    /// <param name="idHook">The id of the event you want to hook</param>
    /// <param name="callback">The callback.</param>
    /// <param name="hInstance">The handle you want to attach the event to, can be null</param>
    /// <param name="threadId">The thread you want to attach the event to, can be null</param>
    /// <returns>a handle to the desired hook</returns>
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);
    /// <summary>
    /// Unhooks the windows hook.
    /// </summary>
    /// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
    /// <returns>True if successful, false otherwise</returns>
    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);
    /// <summary>
    /// Calls the next hook.
    /// </summary>
    /// <param name="idHook">The hook id</param>
    /// <param name="nCode">The hook code</param>
    /// <param name="wParam">The wparam.</param>
    /// <param name="lParam">The lparam.</param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);
    /// <summary>
    /// Loads the library.
    /// </summary>
    /// <param name="lpFileName">Name of the library</param>
    /// <returns>A handle to the library</returns>
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);
    #endregion

I updated the code with IDisposable. I am probably horribly off on what I am supposed to do but its still not working

@Tim If he was talking about how to write kernel drivers to catch keyboard input one might wonder. But the fact that Windows exposes API's in order to do this, doesn't mean anyone using them is up to no good. There are plenty of Nanny programs for children that probably use similar hooks. – Andrew T Finnell May 31, 2011 at 21:31 @Andrew Finnell - I was sort of joking... just the fact that his assembly (I think) is called "keylogger" made me cringe a little bit. – Tim May 31, 2011 at 21:38 @Chris - Implement the Dispose Pattern to make it possible to ensure that unhook is called before the class instance gets finalized instead of during finalization. – hemp May 31, 2011 at 21:43 yeah technically it is a keylogger. but its also a type of nanny program. It does a bunch of stuff but the only problem I have encountered was this keylogging part – Chris May 31, 2011 at 21:54

is just syntactic sugar for:

hhook = SetWindowsHookEx(WH_KEYBOARD_LL, new keyboardHookProc(hookProc), hInstance, 0);

and so the keyboardHookProc object is just local and will get disposed of since SetWindowsHookEx doesn't do anything to actually hold onto it in the managed world.

To fix this, up at the top where you define your member variables, add one more like this:

IntPtr hhook = IntPtr.Zero
private keyboardHookProc hookProcDelegate;

then change your constructor to be:

public globalKeyboardHook()
    hookProcDelegate = hookProc;
    hook();

and then change your hook() method to be:

public void hook()
    IntPtr hInstance = LoadLibrary("User32");
    hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProcDelegate, hInstance, 0);

That way you're using a delegate that is stored as a member variable and will be alive as long as your globalKeyboardHook object is alive.

This solution worked for me, though I had to make my class var static. It was still getting garbage collected until that change. – HotN Dec 5, 2013 at 20:56 @Tim I did the same experiment with my field getting setted in a method and I got the same error. When i put the field in the class constructor, the error don't happened more. Why I had to have the field initialization in the constructor? Don't make sense? If the field is declared in class level but not instantiated in constructor but inside a random method, the garbage collector could not get it because it is in a class level. Why did GC collected it? – Acaz Souza Jul 23, 2014 at 16:32

Sounds to me like you are instantiating a globalKeyboardHook then letting it get garbage collected. I'm guessing you do something like this:

public void InstallHook()
    var hook = new globalKeyboardHook();

You need to keep a reference to the globalKeyboardHook() around to prevent it from being garbage collected.

globalKeyboardHook hook;
public void InstallHook()
    hook = new globalKeyboardHook();
                It will still hit this scenario when the object holding the reference goes out of scope. The solution really needs to include IDisposable.
– hemp
                May 31, 2011 at 21:46

I'd like to add this, for future reference, as it may help understanding Tim's answer, and maybe debugging what's going on, if you have complex code:

callbackOnCollectedDelegate MDA

https://learn.microsoft.com/en-us/dotnet/framework/debug-trace-profile/callbackoncollecteddelegate-mda

even though with the new code I am still getting the mentioned error, as a solution I just kept an instance of the delegate at class scope, now the error does not come up anymore.

//do not forget to declare kbhproc class var   
this.kbhProc = new keyboardHookProc(hookProc);
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, this.kbhProc /*new keyboardHookProc(hookProc)*/, hInstance, 0);

the above code is based on the code of the question.

Umm...this is essentially the same answer that was posted on this question 2.5 years ago, only with less detail. – HotN Dec 5, 2013 at 20:55

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.