How to Close Console Application Gracefully on Windows Shutdown
vb.net, winapi, windows-console
Solution
If Not SetConsoleCtrlHandler(AddressOf Application_ConsoleEvent, True) Then
This is the statement that gets you into trouble. It creates a delegate instance on-the-fly and passes it to unmanaged code. But the garbage collector cannot see the reference held by that unmanaged code. You'll need to store it yourself so it won't be garbage collected. Make that look like this:
Private handler As ConsoleEventDelegate
Sub Main()
handler = AddressOf Application_ConsoleEvent
If Not SetConsoleCtrlHandler(handler, True) Then
'' etc...
The handler variable now keeps it referenced and does so for the life of the program since it is declared in a Module.
You cannot cancel a shutdown btw, but that's another issue.
Problem
I am trying to close my vb.net console app gracefully when windows shutdown occurs. I have found examples that call the Win32 function SetConsoleCtrlHandler that all basically look like this: ``` Module Module1 Public Enum ConsoleEvent CTRL_C_EVENT = 0 CTRL_BREAK_EVENT = 1 CTRL_CLOSE_EVENT = 2 CTRL_LOGOFF_EVENT = 5 CTRL_SHUTDOWN_EVENT = 6 End Enum Private Declare Function SetConsoleCtrlHandler Lib "kernel32" (ByVal handlerRoutine As ConsoleEventDelegate, ByVal add As Boolean) As Boolean Public Delegate Function ConsoleEventDelegate(ByVal MyEvent As ConsoleEvent) As Boolean Sub Main() If Not SetConsoleCtrlHandler(AddressOf Application_ConsoleEvent, True) Then Console.Write("Unable to install console event handler.") End If 'Main loop Do While True Threading.Thread.Sleep(500) Console.WriteLine("Main loop executing") Loop End Sub Public Function Application_ConsoleEvent(ByVal [event] As ConsoleEvent) As Boolean Dim cancel As Boolean = False Select Case [event] Case ConsoleEvent.CTRL_C_EVENT MsgBox("CTRL+C received!") Case ConsoleEvent.CTRL_BREAK_EVENT MsgBox("CTRL+BREAK received!") Case ConsoleEvent.CTRL_CLOSE_EVENT MsgBox("Program being closed!") Case ConsoleEvent.CTRL_LOGOFF_EVENT MsgBox("User is logging off!") Case ConsoleEvent.CTRL_SHUTDOWN_EVENT MsgBox("Windows is shutting down.") ' My cleanup code here End Select Return cancel ' handling the event. End Function ``` This works fine until I incorporate it into muy existing program when I get this exception: CallbackOnCollectedDelegate was detected Message: A callback was made on a garbage collected delegate of type 'AISLogger!AISLogger.Module1+ConsoleEventDelegate::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. Much searching indicates that the problem is caused by the the delegate object not being referenced and so is going out of scope and so being disposed of by the garbage collector. This seems to be confirmed by adding a GC.Collect into the main loop in the example above and getting the same exception when closing the console window or pressing ctrl-C. The trouble is, I don't understand what is meant by 'referencing the delegate'? This sounds to me like assigning a variable to a function??? How can I do this in VB? There are lots of C# examples of this but I can't translate them into VB. Thanks.