1

Possible Duplicate:
CallbackOnCollectedDelegate in globalKeyboardHook was detected

I'm running into an exception with calling my main (and only) form back from being non-visible. I have a key hook to watch for a key that will make it visible again. The problem is however, when the key is pressed and the form goes to load, I run into this exception

A callback was made on a garbage collected delegate of type 'MyProgram!Utilities.globalKeyboardHook+keyboardHookProc::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

Being that this is the first time I've dealt with key hooks, or bringing a form back without the use of another form, I'm at a bit of a loss here. I'm not sure as to what it is trying to get at. Should I just make the opacity of the form to 0, to prevent the program from trying to shut down the form?

public partial class Form1 : Form {

    //Variables

    globalKeyboardHook gkh = new globalKeyboardHook();

    public Form1()
    {
        InitializeComponent();
    }

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

    private void OpacityBar_ValueChanged(object sender, EventArgs e)
    {
        //Do stuff


    private void VisibleTSMI_Click(object sender, EventArgs e)
    {
        //Do more un-important stuff
    }

    void gkh_KeyUp(object sender, KeyEventArgs e)
    {
        if (KeyDown == true)
        {
            this.Visible = true;
        }

        e.Handled = true;
    }

    void gkh_KeyDown(object sender, KeyEventArgs e)
    {
        KeyDown = true;
        e.Handled = true;
    }


    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
    }


}

Please note; There is no more Utilities namespace, as I scratched it after consolidating it with my Form1 code.

Community
  • 1
  • 1

2 Answers2

0

I think you are trying to reach your Utilities instance after deleting the reference to it, for example:

Utilities utilities = new Utilities();
SomeMethodDelegate method = utilities.SomeMethod();
// ...
utilities = someOtherUtilitiesInstanceOrNull;
method(); // This is referencing a method that is now probably collected by the garbage collector.

Maybe it's a better idea to make the Utilities class static, depending on your model of course.

hattenn
  • 4,371
  • 9
  • 41
  • 80
  • `Cannot create an instance of the static class 'MyProgram.Form1.globalKeyboardHook'` The Utilities was a namespace, I consolidated the globalKeyboardHook class into the same area, however this error was thrown trying to make it static. – user1906796 Dec 15 '12 at 21:55
  • You shouldn't try to create an instance of it. You should call the methods like `globalKeyboardHook.someMethod()` directly without doing `globalKeyboardHook instance = new globalKeyboardHook`. – hattenn Dec 15 '12 at 21:59
0

The Problem

Your problem is that you are passing a delegate to unmanaged code, but not keeping a reference to it in your managed code. Since you don't have a reference to it, the GC assumes it's safe to collect.

I can't pinpoint it in your code, since you haven't posted any, but here is a possibility.

You could be creating a delegate implicitly:

SomeUnmanagedMethod(someCallback);

This translates to something like this:

SomeUnmanagedMethod(new SomeDelegate(someCallback));

As you can see, the new instance of the delegate is never referenced and thus goes out of scope and is collected.


The Solution

You need to maintain a reference to your delegate within managed code. One way to do this is to create a static variable in your Utilities class:

static SomeDelegate callback;

You can then store a reference to the delegate, replacing the original code.:

callback = someCallback;
SomeUnmanagedMethod(callback);

Unfortunately, I can't give a more specific answer, since you haven't posted any code.

See also this answer.

Community
  • 1
  • 1
Kendall Frey
  • 43,130
  • 20
  • 110
  • 148
  • I'll see what I can do, in the meantime, I posted my code if you want to take a look. I apologize for not doing so in the first place – user1906796 Dec 15 '12 at 22:13
  • Your problem is on line `hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);`. You should store the `hookProc` delegate in a field of the `globalKeyboardHook` class, rather than creating it on the fly. – Kendall Frey Dec 15 '12 at 22:18
  • I get what you meant by that, thanks. I'll keep this in mind if I run into something like this again. I would up-vote you but apparently I don't have enough rep. Bah. – user1906796 Dec 15 '12 at 23:03
  • You can still mark my post as the answer, if it helped. – Kendall Frey Dec 15 '12 at 23:05