In the project, it is necessary to intercept the keystroke globally, at a certain stage the application on the third press began to issue CallbackOnCollectedDelegate, forced GC.Collect at the end of the keypress event, the error began to fly out immediately after the keystroke. Made an empty project with the same key interception, the situation remained.

Program.cs standard

static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } 

Code Form1

 public partial class Form1 : Form { globalKeyboardHook gkh = new globalKeyboardHook(); public Form1() { InitializeComponent(); gkh.HookedKeys.Add(Keys.F12); gkh.KeyUp += gkh_KeyUp; } void gkh_KeyUp(object sender, KeyEventArgs e) { Console.WriteLine("test"); GC.Collect(); } } 

An exception

 Необработанное исключение: System.NullReferenceException: Ссылка на объект не указывает на экземпляр объекта. в System.StubHelpers.StubHelpers.CheckCollectedDelegateMDA(IntPtr pEntryThunk) 

While running the program, Visual Studio displays this.

I could not find the information for pEntryThunk and in general System.StubHelpers is not connected in the project, I ask for help


globalKeyboardHook - the class used to intercept keys, source code:

https://www.codeproject.com/articles/19004/a-simple-c-global-low-level-keyboard-hook

 using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Windows.Forms; 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 } } 
  • What is globalKeyboardHook ? - Grundy
  • It was not worth trusting code that violates class naming conventions (which is immediately obvious), and does not use IDisposable where there is obvious work with native resources (which, in general, should also be visible, since with Dispose, it would be worthwhile to call Dispose of this thing, but this is already a flaw in your code). - Qwertiy

1 answer 1

Ok, look, the following happens in the hook code:

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

This code is equivalent to this:

 hInstance = LoadLibrary("User32"); keyboardHookProc temp = new keyboardHookProc(hookProc); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, temp, hInstance, 0); 

Those. An instance of the delegate is created, which is passed to the native code. In this case, the managed code reference to the delegate is not saved So this delegate instance can be collected by the garbage collector. And the next time when you try to call a native code, it will not invoke a callback, but will fall with an exception (as in your question).

Fix - to explicitly grab the delegate in the globalKeyboardHook class field:

Add field:

 private keyboardHookProc _keyboardHookProc; 

And rewrite the hook method as

 hInstance = LoadLibrary("User32"); _keyboardHookProc = new keyboardHookProc(hookProc); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardHookProc, hInstance, 0); 

In fact, the problem is mentioned in the comments on the codeproject, and I just copied the fix from there :)

  • Thank you, the problem is solved, why I did not immediately think to look at the comments .... - Hollow