2013-03-09 191 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任務管理器中單擊「結束任務」(並注意如果這樣做,將不會引發事件,這意味着上述建議將不起作用),但它可能不是你真正想要的解決方案。

相關問題