Windows Vista のリスタート/リカバリ処理の開発


環境: Windows Vista、 Visual Studio 2005

こんにちは。

いよいよ Window Vista がリリースされました。今日は、Vista のリスタートとリカバリ機能を使ってみましょう。

ここでは RestartManager については触れませんが、リスタート処理の箇所などは重複した話題でもあり、この周辺のテクノロジーは日本ではあまり大きく取り上げられていませんが、実際の業務アプリを考えた場合、非常に有用な仕組みだと思います。
日本でも、既に「わんくま同盟」の方 (ブログ1, ブログ2, ブログ3 , , ,) などコミュニティの方を中心に、サンプルなどを作成して検証されています。(私自身、一昨年前の PDC に参加できなかったので、実は、セミナーなどで説明はしていても、はじめてわんくま同盟さんで "動く" デモを見せて頂きました。)

もしかすると、コードは少し古い可能性もありますが、米国では以下のビデオキャストなどは参考になると思います。

https://channel9.msdn.com/ShowPost.aspx?PostID=256582

https://channel9.msdn.com/Showpost.aspx?postid=251492

では、サンプルを作ってみましょう。
ここでは、例外発生時にデータのリカバリをおこない、リスタートをおこなって回復するというアプリケーションを作成してみます。

  1. C# の [Windows アプリケーション] を新規作成します
  2. フォームに、障害を発生させるための Button を貼り付けましょう (button1 とします)
    また、ボタンのクリックイベントに以下のように記述し例外を発生させます。

    private void button1_Click(object sender, EventArgs e)
    {
        throw new Exception("テスト例外");
    }
  3. このままだと、処理されなかった例外が自動的にトラップされて情報画面が表示されてしまうので、Program.cs の Main の先頭に以下のように記載します。

    [STAThread]
    static void Main()
    {
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
        ...
  4. では、リカバリの処理を組み込んでいきましょう。
    まず、C# でプラットフォーム API が使えるように以下のネームスペースの参照と、クラスを追加しましょう。
    このサンプルでは、簡単のため、クラスを WindowsApplication1 のネームスペース内 (Form1.cs) にそのまま追加しています。(通常はわけて作成したほうが良いでしょう...)

    using System.Runtime.InteropServices;

    static class VistaRestartRecoveryAPI
    {
        [DllImport("kernel32.dll")]
        internal static extern
            uint RegisterApplicationRecoveryCallback(IntPtr pRecoveryCallback, IntPtr pvParameter, int dwPingInterval, int dwFlags);
        [DllImport("kernel32.dll")]
        internal static extern
            uint ApplicationRecoveryInProgress(out bool pbCanceled);
        [DllImport("kernel32.dll")]
        internal static extern
            uint ApplicationRecoveryFinished(bool pSuccess);

        internal delegate int ApplicationRecoveryCallback(IntPtr pvParameter);
    }
  5. つぎに、Form1 クラスに、以下ようなリカバリ処理をおこなうメソッドを作成します。
    ポイントは、リカバリ (回復) そのものをおこなう作業部分と、リカバリ (回復) 中に状態をレポートするダイアログを表示してユーザからのキャンセルを待機する処理部分の2つがあり、それぞれを処理する必要があるということです。そして後者では、ApplicationRecoveryInProgress の API を使用します。
    実際には、このデータのリカバリ (回復) の処理とキャンセルの確認処理は, Timer を使う, 別スレッドにする, などして別々に処理にしてください。(このコードは暫定です。)
    ここでは、Restart & Recovery の仕組みを理解してもらうことだけを主眼としたいので、極めて暫定的な処理をおこなっています。実際はアプリケーションのニーズに応じ適切な処理を記載してください。
    例えば、実際に障害が発生した場合にはコントロールからデータが読み込めないので、実際には、変更時に変数やディスクに格納したり、Word のように定期的に退避するような処理などを記述することになります。(つまり、現実のアプリケーションではこのようなダメなコードのマネはしないでください。キャンセルボタンを押したときの処理も極めて乱暴に処理しています。)

    private int RecoverData(IntPtr pvParameter)
    {
        // データのリカバリの処理
        System.IO.File.WriteAllText(Environment.CurrentDirectory + @"recovery.log", "退避されたデータ...");

        // 回復のキャンセルを確認する処理
        int i = 4;
        while (i != 0)
        {
            bool bCanceled;
            VistaRestartRecoveryAPI.ApplicationRecoveryInProgress(out bCanceled);
            if (bCanceled)
            {
                MessageBox.Show("キャンセルされました!");
                break;
            }
            System.Threading.Thread.Sleep(3000);
            i--;
        }

        VistaRestartRecoveryAPI.ApplicationRecoveryFinished(true);
        return 0;
    }
  6. つぎに、このアプリケーションの障害発生時にリカバリ処理をおこなうようにします。
    まず準備として、Form1 クラス (Form1.cs) に以下のコールバック用のメンバを登録してください。

    private VistaRestartRecoveryAPI.ApplicationRecoveryCallback mCallback;
  7. つぎに、Form のロードイベントとして以下を記述します。
    (ここも、本来ならば、Program.cs の Main に定義するのが良いでしょうが、今回はここで処理します。)

    private void Form1_Load(object sender, EventArgs e)
    {
        mCallback = new VistaRestartRecoveryAPI.ApplicationRecoveryCallback(this.RecoverData);
        IntPtr del = Marshal.GetFunctionPointerForDelegate(mCallback);
        VistaRestartRecoveryAPI.RegisterApplicationRecoveryCallback(del, IntPtr.Zero, 5000, 1 | 4);
    }

では、実行してみてください。一点注意ですが、ここではデバッグ実行 (vshost での実行) は実施しないでください。(以降も同様です。)

ボタンを押すと、例外が発生して、リカバリデータ (recovery.log) が実行パスに保存されてアプリケーションが終了すると思います。

つぎに、リスタートの仕組みを組み込みます。
リスタートも同様に、Vista の API を使ってアプリケーションのリスタートの登録をおこなっておくという概念ですが、この際、その目印 (アプリケーションが通常起動したかリスタートで起動したかを判断する目印) として、コマンドライン引数を登録することができます。

  1. まずは同様に、以下の API を VistaRestartRecoveryAPI クラスの中に定義しておきましょう。

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    internal static extern
        uint RegisterApplicationRestart(
          string pszCommandline,
          int dwFlags);
  2. また、Form のロードイベントの先頭に以下を追加しましょう。
    リスタート時の目印として、RESTART という引数を設定します。(第2引数の意味については、API リファレンスを参照してください。)

    VistaRestartRecoveryAPI.RegisterApplicationRestart("RESTART", 0);
  3. また、リスタート時の回復処理を Form のロードイベントに追加します。
    今回は暫定で以下の通り処理します。

    string[] args = Environment.GetCommandLineArgs();
    if (args.Length > 1)
    {
        MessageBox.Show("ここで復帰の処理を実施します ...");
    }

    Form のロードイベントは以下の通りになっているはずです。

    private void Form1_Load(object sender, EventArgs e)
    {
        VistaRestartRecoveryAPI.RegisterApplicationRestart("RESTART", 0);

        mCallback = new VistaRestartRecoveryAPI.ApplicationRecoveryCallback(this.RecoverData);
        IntPtr del = Marshal.GetFunctionPointerForDelegate(mCallback);
        VistaRestartRecoveryAPI.RegisterApplicationRecoveryCallback(del, IntPtr.Zero, 5000, 1 | 4);

        string[] args = Environment.GetCommandLineArgs();
        if (args.Length > 1)
        {
            MessageBox.Show("ここで復帰の処理を実施します ...");
        }
    }
  4. これで終了のはずですが、実は、少しコツがあります (私ははまりました)。
    理由は不明ですが、実は、アプリケーションが起動してから少し待たないと、デバッグウィンドウ (debug window) が表示されてしまい、デバッグをおこなうかプロセスを終了せざるをなってしまいます。
    そこで、今回は暫定で、以下のように 60 秒待機するようにしましょう。

    private void button1_Click(object sender, EventArgs e)
    {
        this.Text = "少々お待ちください ...";
        System.Threading.Thread.Sleep(60000);
        this.Text = "Crash !";

        throw new Exception("テスト例外");
    }

これでビルドして実行してみましょう。今回もデバッグ実行はしないようにしてください。

 

Comments (0)

Skip to main content