3

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

この例外が発生したときに既存のプログラムに組み込むまで、これは正常に機能します。

CallbackOnCollectedDelegate が検出されました メッセージ: タイプ 'AISLogger!AISLogger.Module1+ConsoleEventDelegate::Invoke' のガベージ コレクション デリゲートでコールバックが行われました。これにより、アプリケーションのクラッシュ、破損、データ損失が発生する可能性があります。デリゲートをアンマネージ コードに渡す場合、デリゲートが呼び出されないことが保証されるまで、マネージ アプリケーションによって保持される必要があります。

多くの検索は、デリゲート オブジェクトが参照されていないことが問題の原因であり、スコープ外になり、ガベージ コレクターによって破棄されていることを示しています。これは、上記の例のメイン ループに GC.Collect を追加し、コンソール ウィンドウを閉じるか、ctrl-C を押したときに同じ例外を取得することで確認されているようです。問題は、'デリゲートを参照する' の意味がわからないことです。これは、変数を関数に代入するように思えますか??? VBでこれを行うにはどうすればよいですか? これには C# の例がたくさんありますが、それらを VB に翻訳することはできません。

ありがとう。

4

3 に答える 3

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...

ハンドラー変数は、モジュールで宣言されているため、参照を保持し、プログラムの存続期間中保持します。

ところでシャットダウンをキャンセルすることはできませんが、それは別の問題です。

于 2013-03-09T23:53:17.030 に答える
1

関数へのポインターは、.Net ではデリゲートとして表されます。デリゲートは一種のオブジェクトであり、他のオブジェクトと同様に、デリゲートへの参照がない場合、ガベージ コレクションが実行されます。

(AddressOf Application_ConsoleEvent)はデリゲートを作成します。 SetConsoleCtrlHandlerはネイティブ関数であるため、デリゲートを認識しません。生の関数ポインタを受け取ります。したがって、一連の操作は次のとおりです。

  1. (AddressOf Application_ConsoleEvent)デリゲートを作成します。
  2. SetConsoleCtrlHandlerデリゲートを指す生の関数ポインターを受け取り、後で使用するために生のポインターを格納します。
  3. 時を経て。現在、デリゲートを参照しているものは何もないことに注意してください。
  4. ガベージ コレクションが発生し、参照されていないためデリゲートが収集されます。
  5. アプリケーションが閉じようとしているため、Windows は raw 関数ポインターを呼び出して通知しようとします。これは存在しないデリゲートを指しているため、クラッシュします。

デリゲートへの参照を保持するには、変数を宣言する必要があります。私のVBは非常にさびていますが、次のようなものです:

Shared keepAlive As ConsoleEventDelegate

それで

keepAlive = AddressOf ConsoleEventDelegate
If Not SetConsoleCtrlHandler(keepAlive, True) Then
    'etc.
于 2013-03-09T23:54:12.147 に答える