C を使用して単一インスタンス WinUI 3 アプリを作成する#

このハウツーでは、C# とWindows アプリ SDKを使用して単一インスタンスの WinUI アプリを作成する方法を示します。 単一インスタンス アプリでは、一度に実行するアプリのインスタンスは 1 つだけ許可されます。 WinUI アプリは、既定で複数インスタンス化されます。 これにより、同じアプリの複数のインスタンスを同時に起動できます。 これは複数のインスタンスと呼ばれます。 ただし、アプリのユース ケースに基づいて単一インスタンス化を実装することもできます。 単一インスタンス アプリの 2 番目のインスタンスを起動しようとすると、代わりに最初のインスタンスのメイン ウィンドウのみがアクティブになります。 このチュートリアルでは、WinUI アプリで単一インスタンス化を実装する方法について説明します。

この記事では、次のことについて説明します。

  • XAML で生成された Program コードを無効にする
  • リダイレクト用にカスタマイズされた Main メソッドを定義する
  • アプリのデプロイ後に単一インスタンス化をテストする

前提条件

このチュートリアルでは、Visual Studioを使用し、WinUI の空のアプリ テンプレートに基づいて構築します。 WinUI 開発を初めて使用する場合は、「 WinUI 3 プロジェクトを作成して実行する」の手順に従って設定できます。 ここでは、Visual Studioをインストールし、WinUI を使用してアプリを開発できるように構成し、最新バージョンの WinUI とWindows アプリ SDKがあることを確認し、Hello World projectを作成します。

完了したら、ここに戻って、"Hello World" projectを単一インスタンスアプリに変換する方法を学習してください。

注意

この方法は、WinUI に関する Windows ブログ シリーズの アプリを単一インスタンス化する (パート 3) ブログ投稿に基づいています。 これらの記事のコードは、GitHub で入手できます。

自動生成されたプログラム コードを無効にする

ウィンドウを作成する前に、できるだけ早くリダイレクトを確認する必要があります。 これを行うには、プロジェクトファイルに「DISABLE_XAML_GENERATED_MAIN」というシンボルを定義する必要があります。 自動生成されたプログラム コードを無効にするには、次の手順に従います。

  1. ソリューション エクスプローラーでproject名を右クリックし、Edit Project File> を選択します。

  2. DISABLE_XAML_GENERATED_MAIN という記号を定義します。 project ファイルに次の XML を追加します。

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

DISABLE_XAML_GENERATED_MAIN シンボルを追加すると、projectの自動生成されたプログラム コードが無効になります。

Main メソッドを使用して Program クラスを定義する

既定の Main メソッドを実行する代わりに、カスタマイズしたProgram.cs ファイルを作成する必要があります。 Program クラスに追加されたコードを使用すると、WinUI アプリの既定の動作ではないリダイレクトをアプリで確認できます。

  1. ソリューション エクスプローラー に移動し、プロジェクト名を右クリックし、追加 | クラス。

  2. 新しいクラスに Program.cs 名前を付け、 Add を選択します。

  3. Program クラスに次の名前空間を追加します。既存の名前空間がある場合はそれを置き換えてください。

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.UI.Dispatching;
    using Microsoft.UI.Xaml;
    using Microsoft.Windows.AppLifecycle;
    
  4. 空の Program クラスを次のように置き換えます。

    public class Program
    {
        [STAThread]
        static int Main(string[] args)
        {
            WinRT.ComWrappersSupport.InitializeComWrappers();
            bool isRedirect = DecideRedirection();
    
            if (!isRedirect)
            {
                Application.Start((p) =>
                {
                    var context = new DispatcherQueueSynchronizationContext(
                        DispatcherQueue.GetForCurrentThread());
                    SynchronizationContext.SetSynchronizationContext(context);
                    _ = new App();
                });
            }
    
            return 0;
        }
    }
    

    Main メソッドは、アプリが最初のインスタンスにリダイレクトするか、次に定義するDecideRedirection を呼び出した後で新しいインスタンスを開始するかを決定します。

  5. Main メソッドの下にDecideRedirection メソッドを定義します。

    private static bool DecideRedirection()
    {
        bool isRedirect = false;
        AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
        ExtendedActivationKind kind = args.Kind;
        AppInstance keyInstance = AppInstance.FindOrRegisterForKey("MySingleInstanceApp");
    
        if (keyInstance.IsCurrent)
        {
            keyInstance.Activated += OnActivated;
        }
        else
        {
            isRedirect = true;
            RedirectActivationTo(args, keyInstance);
        }
    
        return isRedirect;
    }
    

    DecideRedirection は、アプリ インスタンスを表す一意のキーを登録することによって、アプリが登録されているかどうかを判断します。 キー登録の結果に基づいて、アプリの現在のインスタンスが実行されているかどうかを判断できます。 決定を行った後、メソッドは、アプリが新しいインスタンスを起動し続けるかどうかをリダイレクトするか許可するかを認識します。 リダイレクトが必要な場合は、 RedirectActivationTo メソッドが呼び出されます。

  6. 次に、必要な DllImport ステートメントと共に、DecideRedirection メソッドの下に RedirectActivationTo メソッドを作成します。 Program クラスに次のコードを追加します。

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateEvent(
        IntPtr lpEventAttributes, bool bManualReset,
        bool bInitialState, string lpName);
    
    [DllImport("kernel32.dll")]
    private static extern bool SetEvent(IntPtr hEvent);
    
    [DllImport("ole32.dll")]
    private static extern uint CoWaitForMultipleObjects(
        uint dwFlags, uint dwMilliseconds, ulong nHandles,
        IntPtr[] pHandles, out uint dwIndex);
    
    [DllImport("user32.dll")]
    static extern bool SetForegroundWindow(IntPtr hWnd);
    
    private static IntPtr redirectEventHandle = IntPtr.Zero;
    
    // Do the redirection on another thread, and use a non-blocking
    // wait method to wait for the redirection to complete.
    public static void RedirectActivationTo(AppActivationArguments args,
                                            AppInstance keyInstance)
    {
        redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
        Task.Run(() =>
        {
            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
            SetEvent(redirectEventHandle);
        });
    
        uint CWMO_DEFAULT = 0;
        uint INFINITE = 0xFFFFFFFF;
        _ = CoWaitForMultipleObjects(
           CWMO_DEFAULT, INFINITE, 1,
           [redirectEventHandle], out uint handleIndex);
    
        // Bring the window to the foreground
        Process process = Process.GetProcessById((int)keyInstance.ProcessId);
        SetForegroundWindow(process.MainWindowHandle);
    }
    

    RedirectActivationTo メソッドは、アクティブ化をアプリの最初のインスタンスにリダイレクトする役割を担います。 イベント ハンドルを作成し、アクティブ化をリダイレクトする新しいスレッドを開始し、リダイレクトが完了するまで待機します。 リダイレクトが完了すると、メソッドはウィンドウをフォアグラウンドに移動します。

  7. 最後に、DecideRedirection メソッドの下にヘルパー メソッド OnActivated を定義します。

    private static void OnActivated(object sender, AppActivationArguments args)
    {
        ExtendedActivationKind kind = args.Kind;
    }
    

アプリのデプロイを使用して単一インスタンス化をテストする

ここまでは、Visual Studio内でデバッグすることでアプリをテストしてきました。 ただし、一度に実行できるデバッガーは 1 つだけです。 この制限により、同じprojectを同時に 2 回デバッグできないため、アプリが単一インスタンスかどうかを把握できなくなります。 正確なテストを行うために、アプリケーションをローカルの Windows クライアントに展開します。 展開後、Windows にインストールされている任意のアプリと同様に、デスクトップからアプリを起動できます。

  1. ソリューション エクスプローラーに移動し、project名を右クリックし、Deploy を選択します。

  2. スタート メニューを開き、検索フィールドをクリックします。

  3. 検索フィールドにアプリの名前を入力します。

  4. 検索結果からアプリ アイコンをクリックして、アプリを起動します。

    注意

    リリース モードでアプリがクラッシュする場合は、Windows アプリ SDKでトリミングされたアプリに関する既知の問題がいくつかあります。 projectでトリミングを無効にするには、PublishTrimmed プロパティを、projectの ファイル内のすべてのビルド構成に対して.pubxml に設定します。 詳細については、GitHubのこの問題を参照してください。

  5. 手順 2 から 4 を繰り返して同じアプリを再度起動し、別のインスタンスが開くかどうかを確認します。 アプリが単一インスタンスの場合、新しいインスタンスを開くのではなく、最初のインスタンスがアクティブになります。

    ヒント

    必要に応じて、 OnActivated メソッドにいくつかのログ コードを追加して、既存のインスタンスがアクティブ化されていることを確認できます。 WinUI アプリにILogger 実装を追加する方法については、Copilot に問い合わせてください。

まとめ

ここで説明するすべてのコードは GitHub にあり、元の Windows ブログ シリーズのさまざまな手順の分岐があります。 この操作方法に固有のコードについては、single-instancing ブランチを参照してください。 mainブランチは最も包括的です。 他のブランチは、アプリ アーキテクチャがどのように進化したかを示すことを目的としています。

App instancing with the app lifecycle API (アプリのライフサイクル API によるアプリのインスタンス化)

アプリを単一インスタンスにする (パート 3)

GitHub 上の WinAppSDK-DrumPad サンプル