.NET FrameworkのコンソールアプリケーションでFormClosed的な終了処理を行う方法

この記事は公開から3年以上経過しています。

.NET Frameworkのコンソールアプリケーションを×ボタンなどで閉じる際に後処理を行う方法。

.NETコンソールアプリケーションはFramework(mscoree.dll)によってプロセスメモリ空間にgdi32.dlluser32.dllがロードされているため、CTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENTイベントは発生しません(詳細は参考ウェブサイトのMicrosoft Docsを参照)。これらのイベントを処理する必要がある場合は、コンソールアプリケーション上でHidden Windowを作成してWM_QUERYENDSESSIONWM_ENDSESSIONメッセージを処理する必要があります。
Hidden Windowを使ったWM_QUERYENDSESSIONWM_ENDSESSIONの処理については、こちらをご参照ください。

問題

.NETのコンソールアプリケーションはCtrl+cCtrl+Breakの中断イベントをハンドリングすることは可能だが、×ボタンやAlt+F4などでアプリケーションが終了される場合は、WinFormsのようにFormClosed的なイベントハンドリングを行うことができない。

対応

WinAPIのSetConsoleCtrlHandlerでコンソールイベントハンドラを設定し、ハンドラ内で必要な終了処理を行う。

サンプルソースコード

C#

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleAppCS
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            // コントロールハンドラ設定
            ConsoleAPI.SetConsoleCtrlHandler(ControlHandler, true);

            // ダミー処理
            while (true)
                Console.ReadKey();
        }

        // コントロールハンドラ
        private static bool ControlHandler(ConsoleAPI.CtrlTypes ctrlType)
        {
            switch (ctrlType)
            {
                case ConsoleAPI.CtrlTypes.CTRL_C_EVENT:
                    Console.WriteLine("Ctrl+Cが押されました");
                    // キャンセル
                    return true;
                case ConsoleAPI.CtrlTypes.CTRL_BREAK_EVENT:
                    Console.WriteLine("Ctrl+Breakが押されました");
                    // キャンセル
                    return true;
                case ConsoleAPI.CtrlTypes.CTRL_CLOSE_EVENT:
                // .NETはuser32.dll/gdi32.dllがロードされるためLOGOFF/SHUTDOWNは利用不可
                //case ConsoleAPI.CtrlTypes.CTRL_LOGOFF_EVENT:
                //case ConsoleAPI.CtrlTypes.CTRL_SHUTDOWN_EVENT:
                    // ダミー処理
                    Console.WriteLine("終了しています...");
                    Thread.Sleep(1000);
                    return true;
            }
            return false;
        }
    }

    // ConsoleAPIクラス
    public sealed class ConsoleAPI
    {
        // https://docs.microsoft.com/en-us/windows/console/handlerroutine
        public delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);

        // https://docs.microsoft.com/en-us/windows/console/handlerroutine
        public enum CtrlTypes : uint
        {
            CTRL_C_EVENT = 0,
            CTRL_BREAK_EVENT = 1,
            CTRL_CLOSE_EVENT = 2,
            CTRL_LOGOFF_EVENT = 5,
            CTRL_SHUTDOWN_EVENT = 6
        }

        // https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handlerRoutine, bool add);
    }
}

VB.NET

Imports System.Runtime.InteropServices
Imports System.Threading

Module ConsoleAppVB

    Sub Main()
        ' コントロールハンドラ設定
        ConsoleAPI.SetConsoleCtrlHandler(AddressOf ControlHandler, True)

        ' ダミー処理
        While (True)
            Console.ReadKey()
        End While
    End Sub

    ' コントロールハンドラ
    Private Function ControlHandler(ctrlType As ConsoleAPI.CtrlTypes) As Boolean

        Select Case (ctrlType)
            Case ConsoleAPI.CtrlTypes.CTRL_C_EVENT
                Console.WriteLine("Ctrl+Cが押されました")
                ' キャンセル
                Return True
            Case ConsoleAPI.CtrlTypes.CTRL_BREAK_EVENT
                Console.WriteLine("Ctrl+Breakが押されました")
                ' キャンセル
                Return True
            Case ConsoleAPI.CtrlTypes.CTRL_CLOSE_EVENT
                ' .NETはuser32.dll/gdi32.dllがロードされるため利用不可
                'ConsoleAPI.CtrlTypes.CTRL_LOGOFF_EVENT,
                'ConsoleAPI.CtrlTypes.CTRL_SHUTDOWN_EVENT
                ' ダミー処理
                Console.WriteLine("終了しています...")
                Thread.Sleep(1000)
                Return True
        End Select

        Return False
    End Function

End Module

' ConsoleAPIクラス
Public NotInheritable Class ConsoleAPI

    ' https://docs.microsoft.com/en-us/windows/console/handlerroutine
    Public Delegate Function ConsoleCtrlDelegate(CtrlType As CtrlTypes) As Boolean

    ' https://docs.microsoft.com/en-us/windows/console/handlerroutine
    Public Enum CtrlTypes As UInteger
        CTRL_C_EVENT = 0
        CTRL_BREAK_EVENT = 1
        CTRL_CLOSE_EVENT = 2
        CTRL_LOGOFF_EVENT = 5
        CTRL_SHUTDOWN_EVENT = 6
    End Enum

    ' https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler
    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function SetConsoleCtrlHandler(handlerRoutine As ConsoleCtrlDelegate, add As Boolean) As Boolean
    End Function

End Class

参考ウェブサイトなど

以上です。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする