次の方法で共有


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

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

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

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

前提条件

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

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

Note

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

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

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

  1. ソリューション エクスプローラーでプロジェクト名を右クリックし、[プロジェクト ファイルの編集選択

  2. DISABLE_XAML_GENERATED_MAIN という記号を定義します。 プロジェクト ファイルに次の XML を追加します。

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

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

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 つだけです。 この制限により、同じプロジェクトを同時に 2 回デバッグできないため、アプリが単一インスタンスかどうかが分からなくなります。 正確なテストを行うために、アプリケーションをローカルの Windows クライアントに展開します。 展開後、Windows にインストールされている任意のアプリと同様に、デスクトップからアプリを起動できます。

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

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

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

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

    Note

    リリース モードでアプリがクラッシュする場合は、Windows アプリ SDKでトリミングされたアプリに関する既知の問題がいくつかあります。 プロジェクトの ファイル内のすべてのビルド構成に対して PublishTrimmed プロパティを .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 サンプル