2011年7月14日 星期四

如何得知一個 Console Application 被終止

今天被問了一個問題:如何得知一個 Console Application 被終止。

著實嚇了一跳! 畢竟很常開 Console Application 類型的專案是沒錯啦,不過通常只用來寫些 sample code。

不太會在正式的應用上再寫成 Console Application 了,這種問題感覺就是"那A加你甘丹",還真是連想都沒想過。

因為在放暑假的關係 XD,既然有空檔,那就來研究看看好了,要解決這個問題,初步的構想是從 AppDomain 著手。

透過 Intellisense MSDN 文件發現 AppDomain 是有 event 的,看來可用的 event 有:
  • DomainUnload
  • ProcessExit
  • UnhandledException

好吧,既然有方向那就動工先寫個雛型囉,先來段程式碼:

class Program
    {
        static void Main(string[] args)
        {
            var domain = AppDomain.CurrentDomain;

            domain.DomainUnload += (s, e) =>
            {
                Console.WriteLine("AppDomain Unload");
            };

            domain.ProcessExit += (s, e) =>
            {
                Console.WriteLine("Process Exit");
            };

            domain.UnhandledException += (s, e) =>
            {
                Console.WriteLine("Unhandled Exception");
            };

            Console.WriteLine("按下 [E] 引發 exception 結束程式, 其他任意鍵結束程式");
            
            if (Console.ReadKey().KeyChar == 'E')
            {
                throw new Exception();
            }
        }
    }

這 code 是為了先釐清 AppDomain 事件測試而寫的,執行這個程式的話會有幾個發現:
  1. 因未捕捉到的 Exception 而結束的話,會引發 AppDomain 中的 UnhandledException
  2. 正常的程式結束,會引發 AppDomain 中的 ProcessExit
  3. 經由 Ctrl+C, Ctrl+Break 以及視窗關閉等等操作結束的話,AppDomain 的任何一個事件都抓不到

可是朋友的問題,就是希望能抓到視窗被使用者關閉,而非應用程式自行結束(包含發生 Exception)。

這下囧了,如同第 3 點所發現的,沒有一個事件會捕捉到視窗被關閉,想說那我管他去死好哩

喔,不! 事情當然有轉機的,回到家想一想 google 了一下,發現應由 WinAPI 著手了,最後找到了 SetConsoleCtrlHandler 這個 API,搭配一個自訂的 handler 的話可以達成這位朋友要的效果。

那麼整合使用 SetConsoleCtrlHandler 後的 code 如下:

class Program
    {
        static void Main(string[] args)
        {
            SetConsoleCtrlHandler(t =>
            {
                // 這邊簡單的顯示導致視窗關閉的來源是什麼
                Console.WriteLine(t.ToString());

                // 在這裡處理視窗關閉前想要執行的程式碼

                // 返回 false 將事件交回原處理函式執行正常關閉
                return false;
            }, 
            true);

            Console.WriteLine("按下任意鍵結束程式");
            
            Console.ReadKey();

            Console.WriteLine("程式正常結束");
        }

        #region WinAPI 相關

        [DllImport("Kernel32")]
        public static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);

        public delegate bool ConsoleCtrlDelegate(CtrlTypes ctrlType);

        public enum CtrlTypes
        {
            CTRL_C_EVENT = 0,
            CTRL_BREAK_EVENT,
            CTRL_CLOSE_EVENT,
            CTRL_LOGOFF_EVENT = 5,
            CTRL_SHUTDOWN_EVENT
        }

        #endregion
    }

呼叫 SetConsoleCtrlHandler 時必須傳入一委派,以處裡視窗控制事件,結束時必須返回一布林值,SetConsoleCtrlHandler 回傳值與其餘參數可參考 MSDN 文件,不詳加說明。

SetConsoleCtrlHandler 已經可以準確地抓到 Console Application 的中斷事件,如:Ctrl+C、Ctrl+Break、按下關閉視窗按鈕、以工作管理員結束視窗工作、登出、關機等,實際上測試工作良好,但是須注意無法處理 pskill 這樣的工具所造成的程序中斷喔。

參考資料:

沒有留言: