.NET Frameworkのコンソールアプリケーションでログオフ/シャットダウン/リブート時にFormClosed的な終了処理を行う方法

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

前回の記事に引き続き、コンソールアプリケーションでCTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENTタイミングで処理を行う方法。

本方法ではFormを別スレッドのワーカースレッド上で起動していることから、イベントハンドラもワーカースレッド上で実行される(マルチスレッドである)ことに注意が必要です。両者を同じスレッドで処理したい場合には、参考ウェブサイトに説明のある独自のSynchronizationContextを用意するなどの対策を行ってください。

問題

.NETのコンソールアプリケーションは、SetConsoleCtrlHandler()CTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENTで、ログオフ/シャットダウン/リブート時にイベントハンドリングを行うことができない。

対応

コンソールアプリケーションで非表示フォームを用いて、ログオフ/シャットダウン/リブート時のイベントハンドリングを行う。

サンプルソースコード

WinFormsを利用するため、参照設定でSystem.Windows.Formsを追加する必要があります。

C#

using System;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleAppTest210213
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var evw = new EventWatcher((o, e) =>
            {
                // FormClosed(終了時)に行う処理
                System.IO.File.AppendAllText("closed_log.txt", $"{DateTime.Now}\r\n");
            });

            // ダミーループ
            while (true)
                Console.ReadKey();
        }
    }

    // 終了イベント監視クラス
    internal sealed class EventWatcher
    {
        public EventWatcher(FormClosedEventHandler handler)
        {
            // 別スレッドで非表示フォームを生成
            var t = new Thread((p) =>
            {
                using (var form = new EventWatcherForm())
                {
                    form.FormClosed += handler;
                    form.ShowDialog();
                }
            });
            t.SetApartmentState(ApartmentState.STA);
            t.Name = "EventWatcherThread";
            t.Start();
        }

        // 非表示フォームクラス
        private sealed class EventWatcherForm : Form
        {
            // CreateParams
            protected override CreateParams CreateParams
            {
                get
                {
                    // 非表示フォーム用スタイル設定
                    var cp = base.CreateParams;
                    cp.Style = unchecked((int)(
                        WindowStyles.WS_POPUP
                        | WindowStyles.WS_MAXIMIZEBOX
                        | WindowStyles.WS_SYSMENU
                        | WindowStyles.WS_VISIBLE));
                    cp.ExStyle = unchecked((int)(
                        ExWindowStyles.WS_EX_TOOLWINDOW));
                    cp.Width = 0;
                    cp.Height = 0;
                    return cp;
                }
            }

            // ウィンドウスタイル
            // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
            private enum WindowStyles : long
            {
                WS_MAXIMIZEBOX = 0x00010000L,
                WS_SYSMENU = 0x00080000L,
                WS_VISIBLE = 0x10000000L,
                WS_POPUP = 0x80000000L,
            }

            // 拡張ウィンドウスタイル
            // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
            private enum ExWindowStyles : long
            {
                WS_EX_TOOLWINDOW = 0x00000080L,
            }
        }
    }
}

VB.NET

Imports System.Threading
Imports System.Windows.Forms

Module Module1

    Sub Main()

        Dim evw = New EventWatcher(
            Sub(o, e)
                ' FormClosed(終了時)に行う処理
                System.IO.File.AppendAllText("closed_log.txt", $"{DateTime.Now}{vbCrLf}")
            End Sub)

        ' ダミーループ
        While True
            Console.ReadKey()
        End While

    End Sub

    ' 終了イベント監視クラス
    Private NotInheritable Class EventWatcher

        Public Sub New(handler As FormClosedEventHandler)

            ' 別スレッドで非表示フォームを生成
            Dim t = New Thread(
                Sub(p)
                    Using form As Form = New EventWatcherForm()
                        AddHandler form.FormClosed, handler
                        form.ShowDialog()
                    End Using
                End Sub)
            t.SetApartmentState(ApartmentState.STA)
            t.Name = "EventWatcherThread"
            t.Start()
        End Sub

        ' 非表示フォームクラス
        Private NotInheritable Class EventWatcherForm
            Inherits Form

            ' CreateParams
            Protected Overrides ReadOnly Property CreateParams As CreateParams
                Get
                    ' 非表示フォーム用スタイル設定
                    Dim cp = MyBase.CreateParams
                    Dim style = WindowStyles.WS_POPUP Or WindowStyles.WS_MAXIMIZEBOX Or WindowStyles.WS_SYSMENU Or WindowStyles.WS_VISIBLE
                    cp.Style = WindowStyles.WS_POPUP _
                        Or WindowStyles.WS_MAXIMIZEBOX _
                        Or WindowStyles.WS_SYSMENU _
                        Or WindowStyles.WS_VISIBLE
                    cp.ExStyle = ExWindowStyles.WS_EX_TOOLWINDOW
                    cp.Width = 0
                    cp.Height = 0
                    Return cp
                End Get
            End Property

            ' ウィンドウスタイル
            ' https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
            Private Enum WindowStyles
                WS_MAXIMIZEBOX = &H10000
                WS_SYSMENU = &H80000
                WS_VISIBLE = &H10000000
                WS_POPUP = &H80000000
            End Enum

            ' 拡張ウィンドウスタイル
            ' https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
            Private Enum ExWindowStyles
                WS_EX_TOOLWINDOW = &H80
            End Enum

        End Class

    End Class

End Module

参考ウェブサイトなど

  • Microsoft Docs
    並列コンピューティング SynchronizationContext こそすべて

以上です。

シェアする

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

フォローする