Recently I converted some old c++ application to managed .Net c# one. The code built OK but crashing sometime during execution. It shows me “Unhandled System.NullReferenceException Exception” and I could not find what it was the source of this exception. Multiple try-catch statements did not help. Finally when I stated binaries under Visual Studio debugger and got the following message: “A callback was made on a garbage collected delegate. 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.” I began to understand what is going on. The garbage collector deleted delegate object before it was called. Actually c# delegate method is similar to function pointer in c++ code. The example below generates similar System.NullReferenceException exception I had when ctrl/c was pressed or console window was closed clicking on “X” (close) button:
using System; using System.Runtime.InteropServices; namespace SetConsoleCtrlHandler { class Program { public enum CtrlTypes { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT } public delegate bool HandlerRoutine(CtrlTypes CtrlType); [DllImport(“kernel32.dll”, SetLastError = true)] public static extern bool SetConsoleCtrlHandler(HandlerRoutine hr, bool a); [DllImport(“kernel32.dll”, SetLastError = true)] public static extern int GetLastError(); static void Main(string[] args) { HandlerRoutine hRoutine = new HandlerRoutine(ConsoleCtrlCheck); if (!SetConsoleCtrlHandler(hRoutine, true)) Console.WriteLine(“SetConsoleCtrlHandle error: ” + GetLastError().ToString()); GC.Collect(); Console.ReadKey(); } private static bool ConsoleCtrlCheck(CtrlTypes ctrlType) { switch (ctrlType) { case CtrlTypes.CTRL_C_EVENT: case CtrlTypes.CTRL_BREAK_EVENT: // disable ctrl/c and ctrl/break break; case CtrlTypes.CTRL_LOGOFF_EVENT: case CtrlTypes.CTRL_SHUTDOWN_EVENT: case CtrlTypes.CTRL_CLOSE_EVENT: System.Environment.Exit(1); break; } return true; } } } |
When user pressed any key the delegate object was not called application exits normally.
It is easy to fix the problem adding GC.KeepAlive for delegate object to exclude it from garbage collection. New corrected code is looks like:
using System; using System.Runtime.InteropServices; namespace SetConsoleCtrlHandler { class Program { public enum CtrlTypes { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT } public delegate bool HandlerRoutine(CtrlTypes CtrlType); [DllImport(“kernel32.dll”, SetLastError = true)] public static extern bool SetConsoleCtrlHandler(HandlerRoutine hr, bool a); [DllImport(“kernel32.dll”, SetLastError = true)] public static extern int GetLastError(); static void Main(string[] args) { HandlerRoutine hRoutine = new HandlerRoutine(ConsoleCtrlCheck); if (!SetConsoleCtrlHandler(hRoutine, true)) Console.WriteLine(“SetConsoleCtrlHandle error: ” + GetLastError().ToString()); GC.Collect(); GC.KeepAlive(hRoutine); // protect delegate object from garbage collection Console.ReadKey(); } private static bool ConsoleCtrlCheck(CtrlTypes ctrlType) { switch (ctrlType) { case CtrlTypes.CTRL_C_EVENT: case CtrlTypes.CTRL_BREAK_EVENT: // disable ctrl/c and ctrl/break break; case CtrlTypes.CTRL_LOGOFF_EVENT: case CtrlTypes.CTRL_SHUTDOWN_EVENT: case CtrlTypes.CTRL_CLOSE_EVENT: System.Environment.Exit(1); break; } return true; } } } |