如何在Windows关闭时正常关闭控制台应用程序

时间:2013-03-09 23:23:17

标签: vb.net winapi windows-console

我正在尝试在Windows关闭时正常关闭我的vb.net控制台应用程序。我找到了调用Win32函数SetConsoleCtrlHandler的示例,它们基本上都是这样的:

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现有程序中:

  

检测到CallbackOnCollectedDelegate   消息:对“AISLogger!AISLogger.Module1 + ConsoleEventDelegate :: Invoke”类型的垃圾回收委托进行了回调。这可能会导致应用程序崩溃,损坏和数据丢失。将委托传递给非托管代码时,托管应用程序必须将它们保持活动状态,直到确保它们永远不会被调用为止。

许多搜索表明问题是由未被引用的委托对象引起的,因此超出了范围,因此被垃圾收集器处理掉。这似乎是通过在上面的示例中将GC.Collect添加到主循环中并在关闭控制台窗口或按ctrl-C时获得相同的异常来确认的。麻烦的是,我不明白'引用代表'是什么意思?这听起来像是给一个函数分配一个变量???我怎么能在VB中这样做?有很多C#示例,但我无法将它们翻译成VB。

感谢。

3 个答案:

答案 0 :(得分: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...

处理程序变量现在保持引用,并且在程序的生命周期中这样做,因为它在模块中声明。

你无法取消关机btw,但这是另一个问题。

答案 1 :(得分:1)

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

表达式(AddressOf Application_ConsoleEvent)创建委托。 SetConsoleCtrlHandler是本机函数,因此它不了解委托;它接收原始函数指针。所以操作顺序是:

  1. (AddressOf Application_ConsoleEvent)创建委托。
  2. SetConsoleCtrlHandler接收指向委托的原始函数指针,并存储原始指针供以后使用。
  3. 时间过去了。请注意,现在没有任何内容引用该委托。
  4. 发生垃圾收集并收集委托,因为它未被引用。
  5. 应用程序正在关闭,因此Windows会尝试通过调用原始函数指针来通知您。这指向一个不存在的委托,所以你崩溃了。
  6. 您需要声明一个变量以保留对委托的引用。我的VB很生疏,但有点像:

    Shared keepAlive As ConsoleEventDelegate
    

    然后

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

答案 2 :(得分: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任务管理器中(并注意,如果你这样做, ProcessExit 事件将不会被提出,这意味着上述建议将不起作用),但它可能不是你的解决方案真的很想要。