我正在尝试在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。
感谢。
答案 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
是本机函数,因此它不了解委托;它接收原始函数指针。所以操作顺序是:
(AddressOf Application_ConsoleEvent)
创建委托。SetConsoleCtrlHandler
接收指向委托的原始函数指针,并存储原始指针供以后使用。您需要声明一个变量以保留对委托的引用。我的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 事件将不会被提出,这意味着上述建议将不起作用),但它可能不是你的解决方案真的很想要。