2026年版 WinUIアプリ開発 Tips:AppInstanceで二重起動を防ぐ―シングルインスタンス化の基本

スポンサーリンク

WinUI 3で業務アプリを作っていると、「アプリのアイコンをもう一度押したら、同じ画面がもう1つ起動した」という場面があります。複数起動が便利なアプリもありますが、設定ツール、常駐型ユーティリティ、ローカルDBを扱うアプリなどでは、二重起動が競合やユーザーの混乱につながることがあります。

Windows App SDKのデスクトップアプリは、既定では複数インスタンスを同時に実行できます。そこで使えるのがMicrosoft.Windows.AppLifecycle.AppInstanceです。今回は、単に「2個目を終了する」のではなく、2回目の起動を既存プロセスへリダイレクトするシングルインスタンス化の考え方を整理します。

なぜ「Mutexで終了」だけでは足りないのか

従来のデスクトップアプリでは、Mutexなどを使って「すでに起動していたら2個目を終了する」という実装を見かけます。この方法でもプロセス数は1つにできますが、2回目の起動に意味がある場合は情報を捨ててしまいます。

たとえば、エクスプローラーから関連付けされたファイルを開いた場合や、独自URIからアプリを呼び出した場合です。ユーザーの意図は「アプリを起動する」だけでなく、「このファイルを開く」「このURIへ移動する」にあります。2個目を単純終了すると、その起動理由まで失われます。

Windows App SDKのAppInstanceは、インスタンスへキーを登録し、同じキーを持つ既存インスタンスがあれば、アクティベーション情報をRedirectActivationToAsyncで転送できます。つまり、プロセスは1つに保ちながら、2回目の起動要求は既存アプリへ届けられます。

シングルインスタンス化の基本コード

Microsoftの公式ドキュメントでは、インスタンス制御をXAML初期化前に行うため、カスタムMainを使用する方法が示されています。まずプロジェクトファイルで自動生成されるエントリポイントを無効にします。

<PropertyGroup>
  <DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
</PropertyGroup>

次にProgram.csで、すべての起動に同じキーを使います。

using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using System.Threading;
using WinRT;

public static class Program
{
    private static AppInstance? _instance;

    [STAThread]
    public static async Task Main(string[] args)
    {
        ComWrappersSupport.InitializeComWrappers();

        var activatedArgs =
            AppInstance.GetCurrent().GetActivatedEventArgs();

        _instance =
            AppInstance.FindOrRegisterForKey("main-instance");

        if (!_instance.IsCurrent)
        {
            await _instance.RedirectActivationToAsync(activatedArgs);
            return;
        }

        _instance.Activated += OnActivated;

        Application.Start(_ =>
        {
            var context = new DispatcherQueueSynchronizationContext(
                DispatcherQueue.GetForCurrentThread());

            SynchronizationContext.SetSynchronizationContext(context);
            _ = new App();
        });
    }

    private static void OnActivated(
        object? sender,
        AppActivationArguments args)
    {
        // 2回目以降の起動要求を処理する
        // ファイルやURIの情報は args から判定する
    }
}

コードの流れを順番に見る

GetActivatedEventArgs()は、「なぜアプリが起動されたのか」を取得します。通常起動だけでなく、ファイルやプロトコルなどのアクティベーションを扱うときに重要になります。

FindOrRegisterForKey("main-instance")では、指定したキーを現在のプロセスへ登録しようとします。まだ誰も使っていなければ現在のインスタンスがそのキーを取得します。すでに別プロセスが同じキーを持っていれば、返されたAppInstanceのIsCurrentはfalseになります。

そこで2個目のプロセスはRedirectActivationToAsyncを呼び、今回の起動情報を既存インスタンスへ渡して終了します。既存側はActivatedイベントでその要求を受け取れます。これが「二重起動を拒否する」だけの実装との大きな違いです。

実務では「既存ウィンドウを前面に出す」まで考える

シングルインスタンス化できても、2回目の起動時に既存ウィンドウが最小化されたままでは、ユーザーから見ると「何も起きなかった」ように感じます。そのため実務では、リダイレクトされたActivatedイベントを受けたあと、既存ウィンドウを復元・アクティブ化し、必要なら対象ページへ移動する処理まで設計します。

さらにファイル関連付けを使うアプリなら、args.Kindを確認してFileアクティベーションを処理し、対象ファイルを開く流れへつなげます。独自URIならProtocolアクティベーションを判定します。Windows App SDKでは、こうした完全なアクティベーション情報をAppInstance.GetActivatedEventArgs()から取得するのが基本です。

「常に1プロセス」が正解とは限らない

ここは設計上とても重要です。シングルインスタンス化は便利ですが、すべてのアプリに適用するものではありません。Microsoftの公式ドキュメントでは、同じキーを使うシングルインスタンスだけでなく、ファイルパスなどをキーにして「1ドキュメントにつき1インスタンス」のように振り分ける方法も示されています。

たとえば画像編集や文書編集では、複数ウィンドウ・複数プロセスがユーザーの作業を助ける場合があります。一方、設定管理ツールやトレイ常駐アプリでは1インスタンスのほうが自然です。「二重起動を防ぎたい」から始めるのではなく、ユーザーが同時に何を操作したいかからインスタンスモデルを決めるとよいでしょう。

注意点:UI操作はスレッドを意識する

AppInstance.Activatedで受け取った処理から画面を更新する場合は、UIスレッドで実行する必要があります。公式例でも、リダイレクトされたアクティベーションをUIスレッドで処理することが示されています。実際のアプリでは、MainWindowが持つDispatcherQueueなどを使ってUI側へ処理を渡す構成にすると安全です。

また、カスタムMainを使う場合は、通常のWinUIテンプレートが自動生成していた起動処理の一部を自分で担当します。DISABLE_XAML_GENERATED_MAIN、COMラッパー初期化、Application.Startなどを省略して独自流に書き換えないよう、利用中のWindows App SDKに対応した公式例を確認してください。

WinUI 3のライフサイクルも合わせて理解する

WinUI 3を含むWindows App SDKのデスクトップアプリは、UWPのようにバックグラウンドへ移っただけで自動的にSuspendingされるモデルではありません。通常はユーザーが閉じるかプロセスが終了するまで動き続けます。そのため、「1つの長く動くプロセスへ起動要求を集める」という設計は、デスクトップアプリのライフサイクルとも相性があります。

ただし、異常終了やOS更新、タスクマネージャーによる終了はあり得ます。シングルインスタンスにしたから状態が安全になるわけではありません。重要な編集内容やアプリ状態は、終了時だけに頼らず、重要操作の完了時や定期的なタイミングで保存する設計も検討してください。

まとめ

WinUI 3アプリをシングルインスタンス化するなら、Windows App SDKのAppInstanceを使うことで、単に2個目を終了するだけでなく、その起動要求を既存インスタンスへ引き継げます。基本は、同じキーをFindOrRegisterForKeyで登録し、現在のインスタンスでなければRedirectActivationToAsyncで転送する流れです。

実務では、その先にある「既存ウィンドウをどう見せるか」「ファイルやURIをどう開くか」まで含めて設計することが大切です。インスタンス管理はプロセス数を減らすための仕組みではなく、2回目の起動に込められたユーザーの意図を、どのプロセスへ届けるかを決める仕組みだと考えると理解しやすくなります。

参考資料

Please follow and like us:

コメント

タイトルとURLをコピーしました