アプリ ライフサイクル API を使用したアプリのインスタンス化

アプリのインスタンス化モデルは、アプリのプロセスの複数のインスタンスを同時に実行できるかどうかを決定します。

前提条件

Windows App SDK でアプリ ライフサイクル API を使用するには:

  1. 最新リリースの Windows App SDK をダウンロードしてインストールします。 詳細については、開発者ツールのインストールに関する記事をご覧ください。
  2. 手順に従って、Windows App SDK を使用した新しいプロジェクトを作成するか、既存のプロジェクトで Windows App SDK を使用します。

単一インスタンス アプリ

一度に 1 つのメイン プロセスしか実行できる場合、アプリは単一インスタンス化されます。 単一インスタンスアプリの 2 番目のインスタンスを起動しようとすると、通常、最初のインスタンスのメイン ウィンドウがアクティブになります。 これはメイン プロセスにのみ適用されます。 単一インスタンスアプリでは、複数のバックグラウンド プロセスを作成し、単一インスタンス化と見なされます。

UWP アプリは、既定で単一インスタンス化されます。 ただし、追加のインスタンスを作成するか、代わりに既存のインスタンスをアクティブ化するかは、起動時に決定することで、複数インスタンス化することができます。

Windows 10メール アプリは、単一のインスタンス化されたアプリの良い例です。 初めてメールを起動すると、新しいウィンドウが作成されます。 メールを再度起動しようとすると、代わりに既存の [メール] ウィンドウがアクティブになります。

マルチインスタンス アプリ

メイン プロセスを同時に複数回実行できる場合、アプリは複数インスタンス化されます。 複数インスタンス化されたアプリの 2 つ目のインスタンスを起動しようとすると、新しいプロセスとメイン ウィンドウが作成されます。

従来、アンパックされたアプリは既定で複数インスタンス化されますが、必ずしも単一インスタンス化を実装できます。 通常、これは 1 つの名前付きミューテックスを使用して、アプリが既に実行されているかどうかを示します。

メモ帳は、マルチインスタンス アプリの良い例です。 アプリケーションを起動しようとメモ帳、既に実行されているインスタンスの数に関係なく、メモ帳 の新しいインスタンスが作成されます。

Windows App SDK のインスタンスと UWP のインスタンスの違い

Windows App SDK でのインスタンス化動作は、UWP のモデル、クラスに基づいており、主に次のいくつかの違いがあります。

AppInstance クラス

インスタンスの一覧

  • UWP:GetInstances は、アプリがリダイレクトの可能性のために明示的に登録したインスタンスのみを返します。
  • Windows App SDK:GetInstancesは、キーを登録したかどうかに関知ら、AppInstance API を使用しているアプリの実行中のすべてのインスタンスを返します。 これには、現在のインスタンスを含めできます。 現在のインスタンスを一覧に含める場合は、 を呼び出します AppInstance.GetCurrent 。 同じアプリの異なるバージョンと、異なるユーザーによって起動されたアプリのインスタンスに対して、個別のリストが保持されます。

キーの登録

複数インスタンス化されたアプリの各インスタンスは、 メソッドを使用して任意のキーを登録 FindOrRegisterForKey できます。 キーには固有の意味はありません。アプリでは、必要な形式または方法でキーを使用できます。

アプリのインスタンスは、いつでもキーを設定できますが、インスタンスごとに 1 つのキーしか許可されません。新しい値を設定すると、前の値が上書きされます。

アプリのインスタンスでは、そのキーを、別のインスタンスが既に登録した値と同じ値に設定することはできません。 既存のキーを登録しようとすると、そのキーが既に登録されている FindOrRegisterForKey アプリ インスタンスが返されます。

  • UWP:GetInstancesから返されるリストに含まれるには、インスタンスでキーを登録する必要があります。
  • Windows App SDK:キーの登録は、インスタンスの一覧から切り離されます。 インスタンスを一覧に含めるには、キーを登録する必要はありません。

キーの登録解除

アプリのインスタンスは、そのキーの登録を解除できます。

  • UWP:インスタンスがキーの登録を解除すると、アクティブ化リダイレクトで使用できなくなったので 、GetInstancesから返されるインスタンスの一覧には含まれません。
  • Windows App SDK:キーの登録を解除したインスタンスは、アクティブ化リダイレクトで引き続き使用できます。また、GetInstancesから返されるインスタンスの一覧にも含まれます。

インスタンス リダイレクト ターゲット

アプリの複数のインスタンスは、"アクティブ化リダイレクト" と呼ばれるプロセスを互いにアクティブ化できます。 たとえば、アプリは、起動時にアプリの他のインスタンスが見つからない場合にのみ初期化することで、単一のインスタンス化を実装し、別のインスタンスが存在する場合はリダイレクトして終了することができます。 マルチインスタンス アプリでは、そのアプリのビジネス ロジックに従って、必要に応じてアクティブ化をリダイレクトできます。 アクティブ化が別のインスタンスにリダイレクトされる場合、そのインスタンスのコールバック (他のすべてのアクティブ化シナリオで使用されるのと同じコールバック Activated ) が使用されます。

  • UWP:リダイレクトのターゲットにできるのは、キーを登録したインスタンスのみです。
  • Windows App SDK:任意のインスタンスに、登録されたキーが含まれていますが、リダイレクト ターゲットを指定できます。

リダイレクト後の動作

  • UWP:リダイレクトはターミナル操作です。リダイレクトに失敗した場合でも、アクティブ化をリダイレクトした後にアプリが終了します。

  • Windowsアプリ SDK:Windows App SDK では、リダイレクトはターミナル操作ではありません。 これは、既にメモリを割り当て済みの Win32 アプリを任意に終了する際の潜在的な問題を一部反映していますが、より高度なリダイレクト シナリオをサポートすることもできます。 インスタンスがアクティブ化要求を受け取り、大量の CPU 負荷の高い作業を実行するマルチインスタンス アプリについて考え方を考え出します。 そのアプリは、アクティブ化要求を別のインスタンスにリダイレクトし、その処理を続行できます。 リダイレクト後にアプリが終了した場合、このシナリオは実行できません。

アクティブ化要求は複数回リダイレクトできます。 インスタンス A はインスタンス B にリダイレクトされる可能性があります。これにより、インスタンス C にリダイレクトされる可能性があります。この機能を利用する Windows App SDK アプリは循環リダイレクトから保護する必要があります。上の例で C が A にリダイレクトした場合、無限のアクティブ化ループが発生する可能性があります。 アプリがサポートするワークフローに意味がある内容に応じて、循環リダイレクトを処理する方法を決定する必要があります。

アクティブ化イベント

再アクティブ化を処理するために、アプリはアクティブ化イベントに登録できます。

アクティブ化の処理

この例では、アプリが イベントに登録して処理する方法を示 Activated します。 イベントを受信すると、このアプリはイベント引数を使用して、アクティブ化の原因となるアクションの種類を特定し、適切 Activated に応答します。

int APIENTRY wWinMain(
    _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // Initialize the Windows App SDK framework package for unpackaged apps.
    HRESULT hr{ MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion) };
    if (FAILED(hr))
    {
        OutputFormattedDebugString(
            L"Error 0x%X in MddBootstrapInitialize(0x%08X, %s, %hu.%hu.%hu.%hu)\n",
            hr, majorMinorVersion, versionTag, 
            minVersion.Major, minVersion.Minor, minVersion.Build, minVersion.Revision);
        return hr;
    }

    if (DecideRedirection())
    {
        return 1;
    }

    // Connect the Activated event, to allow for this instance of the app
    // getting reactivated as a result of multi-instance redirection.
    AppInstance thisInstance = AppInstance::GetCurrent();
    auto activationToken = thisInstance.Activated(
        auto_revoke, [&thisInstance](
            const auto& sender, const AppActivationArguments& args)
        { OnActivated(sender, args); }
    );

    // Carry on with regular Windows initialization.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_CLASSNAME, szWindowClass, MAX_LOADSTRING);
    RegisterWindowClass(hInstance);
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    MddBootstrapShutdown();
    return (int)msg.wParam;
}

void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
    int const arraysize = 4096;
    WCHAR szTmp[arraysize];
    size_t cbTmp = arraysize * sizeof(WCHAR);
    StringCbPrintf(szTmp, cbTmp, L"OnActivated (%d)", activationCount++);

    ExtendedActivationKind kind = args.Kind();
    if (kind == ExtendedActivationKind::Launch)
    {
        ReportLaunchArgs(szTmp, args);
    }
    else if (kind == ExtendedActivationKind::File)
    {
        ReportFileArgs(szTmp, args);
    }
}

アクティブ化の種類に基づくリダイレクト ロジック

この例では、アプリは Activated イベントのハンドラーを登録し、アクティブ化イベントの引数をチェックして、アクティブ化を別のインスタンスにリダイレクトするかどうかを決定します。

ほとんどの種類のアクティブ化では、アプリは通常の初期化プロセスを続行します。 ただし、アクティブ化が関連付けられたファイルの種類が開き、このアプリの別のインスタンスで既にファイルが開いている場合、現在のインスタンスはアクティブ化を既存のインスタンスにリダイレクトして終了します。

このアプリでは、キーの登録を使用して、どのファイルがどのインスタンスで開いているか判断します。 インスタンスは、ファイルを開くと、そのファイル名を含むキーを登録します。 その後、他のインスタンスは、登録されているキーを調べて特定のファイル名を探し、他のインスタンスに既に存在していない場合は、そのファイルのインスタンスとして登録できます。

キーの登録自体は Windows App SDK のアプリ ライフサイクル API の一部ですが、キーの内容はアプリ自体内でのみ指定されます。 アプリでは、ファイル名や他の意味のあるデータを登録する必要がありません。 ただし、このアプリでは、特定のニーズとサポートされているワークフローに基づいて、キーを使用して開いているファイルを追跡することを決定しました。

bool DecideRedirection()
{
    // Get the current executable filesystem path, so we can
    // use it later in registering for activation kinds.
    GetModuleFileName(NULL, szExePath, MAX_PATH);
    wcscpy_s(szExePathAndIconIndex, szExePath);
    wcscat_s(szExePathAndIconIndex, L",1");

    // Find out what kind of activation this is.
    AppActivationArguments args = AppInstance::GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind();
    if (kind == ExtendedActivationKind::Launch)
    {
        ReportLaunchArgs(L"WinMain", args);
    }
    else if (kind == ExtendedActivationKind::File)
    {
        ReportFileArgs(L"WinMain", args);

        try
        {
            // This is a file activation: here we'll get the file information,
            // and register the file name as our instance key.
            IFileActivatedEventArgs fileArgs = args.Data().as<IFileActivatedEventArgs>();
            if (fileArgs != NULL)
            {
                IStorageItem file = fileArgs.Files().GetAt(0);
                AppInstance keyInstance = AppInstance::FindOrRegisterForKey(file.Name());
                OutputFormattedMessage(
                    L"Registered key = %ls", keyInstance.Key().c_str());

                // If we successfully registered the file name, we must be the
                // only instance running that was activated for this file.
                if (keyInstance.IsCurrent())
                {
                    // Report successful file name key registration.
                    OutputFormattedMessage(
                        L"IsCurrent=true; registered this instance for %ls",
                        file.Name().c_str());
                }
                else
                {
                    keyInstance.RedirectActivationToAsync(args).get();
                    return true;
                }
            }
        }
        catch (...)
        {
            OutputErrorString(L"Error getting instance information");
        }
    }
    return false;
}

任意のリダイレクト

この例では、より高度なリダイレクト規則を追加することで、前の例を拡張しています。 アプリは引き続き、前の例の開いているファイルチェックを実行します。 ただし、開いているファイル チェックに基づいてリダイレクトしなかった場合、前の例では常に新しいインスタンスが作成されます。この例では、"再利用可能" インスタンスの概念が追加されます。 再利用可能なインスタンスが見つかった場合、現在のインスタンスは再利用可能なインスタンスにリダイレクトされ、終了します。 それ以外の場合は、再利用可能として自身を登録し、通常の初期化を続行します。

ここでも、"再利用可能" インスタンスの概念は、アプリ ライフサイクル API には存在しません。アプリ自体内でのみ作成および使用されます。

int APIENTRY wWinMain(
    _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    // Initialize COM.
    winrt::init_apartment();

    AppActivationArguments activationArgs =
        AppInstance::GetCurrent().GetActivatedEventArgs();

    // Check for any specific activation kind we care about.
    ExtendedActivationKind kind = activationArgs.Kind;
    if (kind == ExtendedActivationKind::File)
    {
        // etc... as in previous scenario.
    }
    else
    {
        // For other activation kinds, we'll trawl all instances to see if
        // any are suitable for redirecting this request. First, get a list
        // of all running instances of this app.
        auto instances = AppInstance::GetInstances();

        // In the simple case, we'll redirect to any other instance.
        AppInstance instance = instances.GetAt(0);

        // If the app re-registers re-usable instances, we can filter for these instead.
        // In this example, the app uses the string "REUSABLE" to indicate to itself
        // that it can redirect to a particular instance.
        bool isFound = false;
        for (AppInstance instance : instances)
        {
            if (instance.Key == L"REUSABLE")
            {
                isFound = true;
                instance.RedirectActivationToAsync(activationArgs).get();
                break;
            }
        }
        if (!isFound)
        {
            // We'll register this as a reusable instance, and then
            // go ahead and do normal initialization.
            winrt::hstring szKey = L"REUSABLE";
            AppInstance::FindOrRegisterForKey(szKey);
            RegisterClassAndStartMessagePump(hInstance, nCmdShow);
        }
    }
    return 1;
}

リダイレクト オーケストレーション

この例では、さらに高度なリダイレクト動作が追加されます。 ここでは、アプリ インスタンスは、特定の種類のすべてのアクティブ化を処理するインスタンスとして登録できます。 アプリのインスタンスは、アクティブ化を受け取った場合、最初に、アクティブ化を処理するために既に登録されている Protocol インスタンスを Protocol 確認します。 見つけた場合は、アクティブ化をそのインスタンスにリダイレクトします。 そうでない場合、現在のインスタンスはアクティブ化のために自身を登録し、他の理由でアクティブ化をリダイレクトする可能性がある追加のロジック (表示されない Protocol ) を適用します。

void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
    const ExtendedActivationKind kind = args.Kind;

    // For example, we might want to redirect protocol activations.
    if (kind == ExtendedActivationKind::Protocol)
    {
        auto protocolArgs = args.Data().as<ProtocolActivatedEventArgs>();
        Uri uri = protocolArgs.Uri();

        // We'll try to find the instance that handles protocol activations.
        // If there isn't one, then this instance will take over that duty.
        auto instance = AppInstance::FindOrRegisterForKey(uri.AbsoluteUri());
        if (!instance.IsCurrent)
        {
            instance.RedirectActivationToAsync(args).get();
        }
        else
        {
            DoSomethingWithProtocolArgs(uri);
        }
    }
    else
    {
        // In this example, this instance of the app handles all other
        // activation kinds.
        DoSomethingWithNewActivationArgs(args);
    }
}

の UWP バージョンとは異 RedirectActivationTo なり、Windows App SDK のRedirectActivationToの実装では、アクティブ化をリダイレクトするときにイベント引数を明示的に渡す必要があります。 uwp はアクティベーションを厳密に制御し、正しいアクティベーション引数が正しいインスタンスに渡されるようにするため、Windows App SDK のバージョンは多くのプラットフォームをサポートし、UWP 固有の機能に依存することはできないため、この操作が必要になります。 このモデルの利点の1つは、Windows App SDK を使用するアプリで、ターゲットインスタンスに渡される引数を変更または置き換えることができることです。

ブロックせずにリダイレクト

ほとんどのアプリは、不要な初期化作業を実行する前に、できるだけ早くリダイレクトする必要があります。 アプリの種類によっては、初期化ロジックが STA スレッドで実行され、ブロックすることはできません。 RedirectActivationToAsync メソッドは非同期であり、呼び出し元のアプリはメソッドが完了するまで待機する必要があります。そうしないと、リダイレクトは失敗します。 ただし、非同期呼び出しを待機すると、STA はブロックされます。 このような場合は、別のスレッドで RedirectActivationToAsync を呼び出し、呼び出しが完了したときにイベントを設定します。 次に、CoWaitForMultipleObjects などの非ブロッキング Api を使用して、そのイベントを待機します。 WPF アプリの C# のサンプルを次に示します。

private static bool DecideRedirection()
{
    bool isRedirect = false;

    // Find out what kind of activation this is.
    AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind;
    if (kind == ExtendedActivationKind.File)
    {
        try
        {
            // This is a file activation: here we'll get the file information,
            // and register the file name as our instance key.
            if (args.Data is IFileActivatedEventArgs fileArgs)
            {
                IStorageItem file = fileArgs.Files[0];
                AppInstance keyInstance = AppInstance.FindOrRegisterForKey(file.Name);

                // If we successfully registered the file name, we must be the
                // only instance running that was activated for this file.
                if (keyInstance.IsCurrent)
                {
                    // Hook up the Activated event, to allow for this instance of the app
                    // getting reactivated as a result of multi-instance redirection.
                    keyInstance.Activated += OnActivated;
                }
                else
                {
                    isRedirect = true;

                    // Ensure we don't block the STA, by doing the redirect operation
                    // in another thread, and using an event to signal when it has completed.
                    redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
                    if (redirectEventHandle != IntPtr.Zero)
                    {
                        Task.Run(() =>
                        {
                            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
                            SetEvent(redirectEventHandle);
                        });
                        uint CWMO_DEFAULT = 0;
                        uint INFINITE = 0xFFFFFFFF;
                        _ = CoWaitForMultipleObjects(
                            CWMO_DEFAULT, INFINITE, 1, 
                            new IntPtr[] { redirectEventHandle }, out uint handleIndex);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Error getting instance information: {ex.Message}");
        }
    }

    return isRedirect;
}

リダイレクトの登録を解除します

キーを登録したアプリは、いつでもそのキーの登録を解除できます。 この例では、現在のインスタンスに、特定のファイルが開かれていることを示すキーが既に登録されていることを前提としています。これにより、そのファイルを開こうとすると、そのファイルを開こうとすると、 そのファイルが閉じている場合は、ファイル名を含むキーを削除する必要があります。

void CALLBACK OnFileClosed(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    AppInstance::GetCurrent().UnregisterKey();
}

警告

キーはプロセスが終了したときに自動的に登録が解除されますが、終了したインスタンスが登録解除される前に、別のインスタンスが終了したインスタンスへのリダイレクトを開始した可能性があります。 この可能性を軽減するために、アプリは Unregisterkey を使用して、終了する前にキーを手動で登録解除することができます。これにより、アプリは、終了処理中ではない別のアプリにライセンス認証をリダイレクトできるようになります。

インスタンス情報

Windows。AppLifeycleクラスは、アプリの1つのインスタンスを表します。 現在のプレビューでは、には、 AppInstance アクティベーションのリダイレクトをサポートするために必要なメソッドとプロパティのみが含まれています。 今後のリリースで AppInstance は、が展開され、アプリインスタンスに関連する他のメソッドとプロパティが含まれるようになります。

void DumpExistingInstances()
{
    for (AppInstance const& instance : AppInstance::GetInstances())
    {
        std::wostringstream sStream;
        sStream << L"Instance: ProcessId = " << instance.ProcessId
            << L", Key = " << instance.Key().c_str() << std::endl;
        ::OutputDebugString(sStream.str().c_str());
    }
}