.NET TIPS

コンソール・アプリケーションの強制終了時に処理を行うには?(Win32編)[C#、VB]

デジタルアドバンテージ 遠藤 孝信
2009/07/23

 「TIPS:コンソール・アプリケーションで[閉じる]ボタンを無効にするには?」では、コンソール・アプリケーションの後処理を実行可能にするため、[×]ボタン([閉じる]ボタン)を無効にすることにより、アプリケーションが強制終了されないようにする方法を示したが、Win32 APIを利用すれば、[×]ボタンがクリックされたときに処理を実行することもできる。

 これには、Win32 APIである「SetConsoleCtrlHandler関数」を使用する。これは、コンソール・アプリケーションで[Ctrl]+[C]キーが押されたり、コマンド・プロンプトが閉じられたりする際に呼び出されるハンドラ(=特定のメソッド)を登録するための関数だ。

 ハンドラは、SetConsoleCtrlHandler関数の第1引数で指定する。C言語などでは「HandlerRoutine関数」へのポインタを指定することになるが、.NETでは、これをメソッドのデリゲートで代用する。第2引数には、ハンドラを登録するという意味のtrueを指定する。ハンドラが呼び出される際には、アプリケーションが閉じられる要因を示す値が、関数(メソッド)のパラメータに渡される。

SetConsoleCtrlHandler関数を使用したサンプル・プログラム

 以下では、「TIPS:コンソール・アプリケーションの強制終了時に処理を行うには?」で示したサンプル・プログラムを、SetConsoleCtrlHandler関数を使用して書き換えたものを示す。

// ctrlhandle.cs

using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;

// 簡単なログ・クラス
public class MyLogger {

  StringBuilder log = new StringBuilder();

  // ログの追加
  public void Add(string line) {
    log.AppendLine(line);
  }

  // ログの保存(ログ・ファイルの名前は「yyyymmdd.txt」の形式)
  public void Save() {
    string file = DateTime.Now.ToString("yyyyMMdd") + ".txt";
    Encoding enc = Encoding.GetEncoding("Shift_JIS");

    // 追加書き込み(文字コードはShift-JISを使用)
    using (StreamWriter sw = new StreamWriter(file, true, enc)) {
      sw.Write(log.ToString());
    }
  }
}

// ハンドラ・ルーチンに渡される定数の定義
public enum CtrlTypes
{
  CTRL_C_EVENT        = 0,
  CTRL_BREAK_EVENT    = 1,
  CTRL_CLOSE_EVENT    = 2,
  CTRL_LOGOFF_EVENT   = 5,
  CTRL_SHUTDOWN_EVENT = 6
}

class MyProgram {

  // Win32 APIであるSetConsoleCtrlHandler関数の宣言
  [DllImport("Kernel32")]
  static extern bool
      SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);

  // SetConsoleCtrlHandler関数にメソッド(ハンドラ・ルーチン)を
  // 渡すためのデリゲート型
  delegate bool HandlerRoutine(CtrlTypes CtrlType);

  MyLogger logger = new MyLogger();
  HandlerRoutine myHandlerDele;

  MyProgram() {
    // myHandlerメソッドの登録
    myHandlerDele = new HandlerRoutine(myHandler);
    SetConsoleCtrlHandler((myHandlerDele, true);
  }

  void Run() {
    while (true) {
      string line = DateTime.Now + " 出力";
      Console.WriteLine(line);
      logger.Add(line); // ログの追加
      System.Threading.Thread.Sleep(2000); // 2秒間待つ
    }
  }

  // ハンドラ・ルーチン
  bool myHandler(CtrlTypes ctrlType)
  {
    logger.Add("強制終了:" + ctrlType); // ログの追加
    logger.Save(); // ログの保存
    return false;
  }

  static void Main() {
    (new MyProgram()).Run();
  }
}

// コンパイル方法:csc ctrlhandle.cs
強制終了時に処理を行うC#のサンプル・プログラム(ctrlhandler.cs)

' ctrlhandler.vb

Imports System
Imports System.IO
Imports System.Text
Imports System.Runtime.InteropServices

' 簡単なログ・クラス
Public Class MyLogger
  Dim log As New StringBuilder()

  ' ログの追加
  Public Sub Add(ByVal line As String)
    log.AppendLine(line)
  End Sub

  ' ログの保存(ログ・ファイルの名前は「yyyymmdd.txtの」形式)
  Public Sub Save()
    Dim file As String = DateTime.Now.ToString("yyyyMMdd") + ".txt"
    Dim enc As Encoding = Encoding.GetEncoding("Shift_JIS")

    ' 追加書き込み(文字コードはShift-JISを使用)
    Using sw As New StreamWriter(file, True, enc)
      sw.Write(log.ToString())
    End Using
  End Sub
End Class

' ハンドラ・ルーチンに渡される定数の定義
Public Enum CtrlTypes
  CTRL_C_EVENT        = 0
  CTRL_BREAK_EVENT    = 1
  CTRL_CLOSE_EVENT    = 2
  CTRL_LOGOFF_EVENT   = 5
  CTRL_SHUTDOWN_EVENT = 6
End Enum

Class MyProgram

  ' Win32 APIであるSetConsoleCtrlHandler関数の宣言
  <DllImport("Kernel32")> _
  Shared Function SetConsoleCtrlHandler( _
    ByVal Handler As HandlerRoutine, _
    ByVal Add As Boolean) As Boolean
  End Function

  ' SetConsoleCtrlHandler関数にメソッド(ハンドラ・ルーチン)を
  '渡すためのデリゲート型
  Delegate Function _
      HandlerRoutine(ByVal CtrlType As CtrlTypes) As Boolean

  Dim logger As New MyLogger()
  Dim myHandlerDele As HandlerRoutine

  Sub New()
    ' myHandlerメソッドの登録
    myHandlerDele = New HandlerRoutine(AddressOf myHandler)
    SetConsoleCtrlHandler(myHandlerDele, True)
  End Sub

  Sub Run()
    While True
      Dim line As String = DateTime.Now + " 出力"
      Console.WriteLine(line)
      logger.Add(line) ' ログの追加
      System.Threading.Thread.Sleep(2000) ' 2秒間待つ
    End While
  End Sub

  ' ハンドラ・ルーチン
  Function myHandler(ByVal ctrlType As CtrlTypes) As Boolean
    logger.Add("強制終了:" & ctrlType.ToString()) ' ログの追加
    logger.Save() ' ログの保存
    Return False
  End Function

  Shared Sub Main()
    Dim p As New MyProgram()
    p.Run()
  End Sub
End Class

' コンパイル方法:vbc ctrlhandler.vb
強制終了時に処理を行うVBのサンプル・プログラム(ctrlhandler.vb)

 このプログラムでは、ハンドラ(myHandlerメソッド)にパラメータとして渡される値を、列挙型により事前に定義している。各値の詳細については、HandlerRoutine関数の説明を参照してほしい。

 上記のプログラムを実行し、プログラムを[Ctrl]+[C]キーで終了した場合には、出力されるログの内容は次のようになる。

2009/07/23 12:22:26 出力
2009/07/23 12:22:28 出力
2009/07/23 12:22:30 出力
強制終了:CTRL_C_EVENT
[Ctrl]+[C]キーで終了した場合のログ出力例

 また、プログラムを実行したまま、そのコマンド・プロンプトを閉じると、ログは次のようになる。

2009/07/23 12:22:46 出力
2009/07/23 12:22:48 出力
2009/07/23 12:22:50 出力
強制終了:CTRL_CLOSE_EVENT
コマンド・プロンプトを閉じた場合のログ出力例

 各ログの最後の行で出力している変数ctrlTypeの値が異なっているのを確認できる。End of Article

カテゴリ:コンソール・アプリケーション 処理対象:画面
カテゴリ:クラス・ライブラリ 処理対象:Win32 API
使用ライブラリ:DllImport属性(System.Runtime.InteropServices名前空間)
関連TIPS:コンソール・アプリケーションで[閉じる]ボタンを無効にするには?
関連TIPS:コンソール・アプリケーションの強制終了時に処理を行うには?

この記事と関連性の高い別の.NET TIPS
コンソール・アプリケーションで[閉じる]ボタンを無効にするには?
コンソール・アプリケーションの強制終了時に処理を行うには?
タイトル・バーの[閉じる]ボタンのみを無効にするには?
Visual Studioでコンソール・アプリケーション実行時にコマンド・プロンプトを閉じないようにするには?
Win32 APIやDLL関数を呼び出すには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」

更新履歴
【2009/7/24】サンプル・プログラムにおいて、ガベージ・コレクション発生時に正しく動作しないという問題が見つかりました。このため、SetConsoleCtrlHandler関数の引数部分でインスタンス化していたハンドラのデリゲート・オブジェクトを、クラスのフィールドに保持するように修正しました。お詫びして訂正させていただきます。


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間