2013-03-09 153 views
3

我想在Windows关机发生时正常关闭我的vb.net控制台应用程序。我发现,调用Win32函数SetConsoleCtrlHandler例子,所有的基本上是这样的:检测如何在Windows关机时关闭控制台应用程序

CallbackOnCollectedDelegate:

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 

,直到我将其纳入MUY现有程序,当我得到这个例外,这工作正常 消息:对“AISLogger!AISLogger.Module1 + ConsoleEventDelegate :: Invoke”类型的垃圾收集代理进行回调。这可能会导致应用程序崩溃,损坏和数据丢失。在将代理传递给非托管代码时,它们必须由托管应用程序保持活动状态,直到确保它们永远不会被调用。

大量搜索表明问题是由于未引用委托对象造成的,因此超出了范围,因此被垃圾回收器处置。这似乎可以通过在上面的例子中将GC.Collect添加到主循环中并在关闭控制台窗口或按Ctrl-C时获得相同的异常来确认。问题是,我不明白“引​​用委托”的含义是什么?这听起来像给我分配一个变量的函数?我怎样才能在VB中做到这一点?有很多C#的例子,但我不能将它们转换成VB。

谢谢。

回答

3
If Not SetConsoleCtrlHandler(AddressOf Application_ConsoleEvent, True) Then 

这是让你陷入困境的说法。它会即时创建委托实例并将其传递给非托管代码。但垃圾收集器无法查看该非托管代码所持有的引用。你需要自己储存它,这样它不会被垃圾收集。做出这个样子:

Private handler As ConsoleEventDelegate 

Sub Main() 
    handler = AddressOf Application_ConsoleEvent 
    If Not SetConsoleCtrlHandler(handler, True) Then 
     '' etc... 

处理器变量现在保持它的引用,这样做的,因为它是在一个模块中声明该程序的寿命。

你不能取消关闭顺便说一句,但这是另一个问题。

+0

谢谢,汉斯,那很完美。我已经半天试图弄清楚我做错了什么! – Guy 2013-03-10 01:35:15

1

指向函数的指针在.Net中表示为代表。委托是一种对象,和其他对象一样,如果没有对它的引用,那么它将被垃圾收集。

表达式(AddressOf Application_ConsoleEvent)创建委托。 SetConsoleCtrlHandler是一个本地函数,所以它不理解代表;它接收一个原始函数指针。所以操作顺序是:

  1. (AddressOf Application_ConsoleEvent)创建委托。
  2. SetConsoleCtrlHandler接收指向委托的原始函数指针,并存储原始指针供以后使用。
  3. 时间流逝。请注意,现在没有任何引用委托。
  4. 发生垃圾收集,并收集委托,因为它未被引用。
  5. 应用程序正在关闭,因此Windows会尝试通过调用原始函数指针来通知您。这指向一个不存在的委托,所以你崩溃了。

您需要声明一个变量来保持对委托的引用。我的VB是很生疏,但类似:

Shared keepAlive As ConsoleEventDelegate 

然后

keepAlive = AddressOf ConsoleEventDelegate 
If Not SetConsoleCtrlHandler(keepAlive, True) Then 
    'etc. 
+0

谢谢arx。我接受汉斯的回答只是因为他看起来是第一个,而他的VB代码还是很棒,但你的回答肯定会解决我的问题。 – Guy 2013-03-10 01:36:42

0

这样做的最简单的方法可能是处理AppDomain.ProcessExit event,这提高了应用程序的父进程退出时。

For example: 
     Module MyApp 

     Sub Main() 
      ' Attach the event handler method 
      AddHandler AppDomain.CurrentDomain.ProcessExit, AddressOf MyApp_ProcessExit 

      ' Do something 
      ' ... 

      Environment.Exit(0) 
     End Sub 

     Private Sub MyApp_ProcessExit(sender As Object, e As EventArgs) 
      Console.WriteLine("App Is Exiting...") 
     End Sub 

    End Module 

但调用Environment.Exit可能不是你原来的问题的最佳解决方案。一般来说,the only time it is necessary to use this method is when there might be other foreground threads running。在这种情况下,值得研究如何优雅地终止其他线程,而不采取严厉的措施来杀死整个过程。

Environment.Exit,尽管名称有些令人愉悦,但却是一种非常残酷的措施。它不如在Windows任务管理器中单击“结束任务”(并注意如果这样做,将不会引发事件,这意味着上述建议将不起作用),但它可能不是你真正想要的解决方案。

相关问题