App-Instanziierung mit der App-Lebenszyklus-API

Das Instanzierungsmodell einer Anwendung bestimmt, ob mehrere Instanzen des Prozesses Ihrer Anwendung gleichzeitig laufen können.

Voraussetzungen

So verwenden Sie die App Lifecycle API im Windows App SDK:

  1. Laden Sie das neueste experimentelle Release des Windows App SDK herunter und installieren Sie es. Weitere Informationen finden Sie unter Installieren von Tools für das Windows App SDK.
  2. Befolgen Sie die Anweisungen zum Erstellen Ihres ersten WinUI 3-Projekts oder zum Verwenden des Windows App SDK in einem vorhandenen Projekt.

Einzelinstanz-Anwendungen

Hinweis

Ein Beispiel für die Implementierung von Einzelinstanzerstellung in einer WinUI 3-Anwendung mit C# finden Sie unter „Making the app single-instanced“ im Windows Developer Blog.

Bei Einzelinstanz-Anwendungen kann nur ein Hauptprozess zur gleichen Zeit laufen. Der Versuch, eine zweite Instanz einer Einzelinstanz-Anwendung zu starten, führt normalerweise dazu, dass stattdessen das Hauptfenster der ersten Instanz aktiviert wird. Beachten Sie, dass dies nur für den Hauptprozess gilt. Einzelinstanz-Anwendungen können mehrere Hintergrundprozesse erstellen und werden trotzdem als Einzelinstanz-Anwendungen betrachtet.

UWP-Apps sind standardmäßig Einzelinstanz-Anwendungen, haben aber die Möglichkeit, mehrere Instanzen zu erstellen, indem sie beim Start entscheiden, ob sie eine zusätzliche Instanz erstellen oder stattdessen eine bestehende Instanz aktivieren.

Die Windows-Mail-Anwendung ist ein gutes Beispiel für eine Einzelinstanz-Anwendung. Wenn Sie Mail zum ersten Mal starten, wird ein neues Fenster erstellt. Wenn Sie versuchen, Mail erneut zu starten, wird stattdessen das vorhandene Mail-Fenster aktiviert.

Multi-Instanz-Anwendungen

Bei Multi-Instanz-Anwendungen kann der Hauptprozess mehrmals gleichzeitig ausgeführt werden. Der Versuch, eine zweite Instanz einer Multi-Instanz-Anwendung zu starten, erzeugt einen neuen Prozess und ein neues Hauptfenster.

Traditionell sind entpackte Anwendungen standardmäßig Multi-Instanz-Anwendungen, können aber bei Bedarf auch Einzelinstanzerstellung implementieren. Normalerweise wird dazu ein einzelner benannter Mutex verwendet, der anzeigt, ob eine Anwendung bereits läuft.

Notepad ist ein gutes Beispiel für eine Multi-Instanz-Anwendung. Jedes Mal, wenn Sie versuchen, Notepad zu starten, wird eine neue Instanz von Notepad erstellt, unabhängig davon, wie viele Instanzen bereits ausgeführt werden.

Wie sich die Windows App SDK-Instanzierung von der UWP-Instanzierung unterscheidet

Das Instanzierungsverhalten im Windows App SDK basiert auf dem UWP-Modell, der Klasse, jedoch mit einigen wichtigen Unterschieden:

AppInstance-Klasse

Liste der Instanzen

  • UWP: GetInstances gibt nur die Instanzen zurück, die die App explizit für eine mögliche Umleitung registriert hat.
  • Windows App SDK: GetInstances gibt alle laufenden Instanzen der Anwendung zurück, die die AppInstance-API verwenden, unabhängig davon, ob sie einen Schlüssel registriert haben oder nicht. Dazu kann auch die aktuelle Instanz gehören. Wenn Sie möchten, dass die aktuelle Instanz in die Liste aufgenommen wird, rufen Sie AppInstance.GetCurrent auf. Getrennte Listen werden für verschiedene Versionen derselben Anwendung sowie für Instanzen von Anwendungen geführt, die von verschiedenen Benutzern gestartet wurden.

Registrierung von Schlüsseln

Jede Instanz einer Multi-Instanced-App kann einen beliebigen Schlüssel über die Methode FindOrRegisterForKey registrieren. Schlüssel haben keine inhärente Bedeutung; Anwendungen können Schlüssel in jeder beliebigen Form oder Weise verwenden.

Eine Instanz einer Anwendung kann ihren Schlüssel jederzeit setzen, aber für jede Instanz ist nur ein Schlüssel erlaubt; das Setzen eines neuen Wertes überschreibt den vorherigen Wert.

Eine Instanz einer Anwendung kann ihren Schlüssel nicht auf denselben Wert setzen, den eine andere Instanz bereits registriert hat. Der Versuch, einen vorhandenen Schlüssel zu registrieren, führt dazu, dass FindOrRegisterForKey die App-Instanz zurückgibt, die diesen Schlüssel bereits registriert hat.

  • UWP: Eine Instanz muss einen Schlüssel registrieren, um in die Liste aufgenommen zu werden, die von GetInstances zurückgegeben wird.
  • Windows App SDK: Die Registrierung eines Schlüssels ist von der Liste der Instanzen entkoppelt. Eine Instanz muss keinen Schlüssel registrieren, um in die Liste aufgenommen zu werden.

Aufheben der Registrierung von Schlüsseln

Eine Instanz einer Anwendung kann ihren Schlüssel abmelden.

  • UWP: Wenn eine Instanz ihren Schlüssel abmeldet, steht sie nicht mehr für die Aktivierungsumleitung zur Verfügung und ist nicht in der Liste der Instanzen enthalten, die von GetInstances zurückgegeben wird.
  • Windows App SDK: Eine Instanz, deren Schlüssel nicht mehr registriert ist, steht weiterhin für die Aktivierungsumleitung zur Verfügung und ist weiterhin in der Liste der Instanzen enthalten, die von GetInstances zurückgegeben wird.

Ziele der Instanzumleitung

Mehrere Instanzen einer Anwendung können sich gegenseitig aktivieren, ein Prozess, der als „Aktivierungsumleitung“ bezeichnet wird. So könnte eine Anwendung beispielsweise Single Instance implementieren, indem sie sich nur dann initialisiert, wenn beim Start keine anderen Instanzen der Anwendung gefunden werden, und stattdessen umleitet und beendet, wenn eine andere Instanz existiert. Bei Anwendungen mit mehreren Standorten können Aktivierungen entsprechend der Geschäftslogik der jeweiligen Anwendung umgeleitet werden. Wenn eine Aktivierung an eine andere Instanz weitergeleitet wird, verwendet sie den Activated Callback dieser Instanz, den gleichen Callback, der auch in allen anderen Aktivierungsszenarien verwendet wird.

  • UWP: Nur Instanzen, die einen Schlüssel registriert haben, können ein Ziel für eine Umleitung sein.
  • Windows App SDK: Jede Instanz kann ein Umleitungsziel sein, unabhängig davon, ob sie einen registrierten Schlüssel hat oder nicht.

Verhalten nach der Umleitung

  • UWP: Die Umleitung ist ein Endvorgang; die Anwendung wird nach der Umleitung der Aktivierung beendet, auch wenn die Umleitung fehlgeschlagen ist.

  • Windows App SDK: Im Windows App SDK ist die Umleitung keine Terminaloperation. Dies spiegelt zum Teil die potenziellen Probleme bei der willkürlichen Beendigung einer Win32-Anwendung wider, die möglicherweise bereits Speicher zugewiesen hat, ermöglicht aber auch die Unterstützung anspruchsvollerer Umleitungsszenarien. Nehmen wir eine Anwendung mit mehreren Instanzen, bei der eine Instanz eine Aktivierungsanforderung erhält, während sie eine große Menge an CPU-intensiver Arbeit ausführt. Diese Anwendung kann die Aktivierungsanforderung an eine andere Instanz weiterleiten und ihre Bearbeitung fortsetzen. Dieses Szenario wäre nicht möglich, wenn die Anwendung nach der Umleitung beendet würde.

Eine Aktivierungsanfrage kann mehrfach umgeleitet werden. Instanz A könnte zu Instanz B umleiten, die wiederum zu Instanz C umleiten könnte. Windows App SDK-Anwendungen, die diese Funktionalität nutzen, müssen sich vor zirkulären Umleitungen schützen – wenn C im obigen Beispiel zu A umleitet, besteht die Gefahr einer unendlichen Aktivierungsschleife. Es ist Sache der Anwendung, festzulegen, wie die zirkuläre Umleitung gehandhabt werden soll, je nachdem, was für die von der Anwendung unterstützten Arbeitsabläufe sinnvoll ist.

Aktivierungsereignisse

Um die Reaktivierung zu handhaben, kann sich die App für ein Aktiviert-Ereignis registrieren.

Beispiele

Handhabung von Aktivierungen

Dieses Beispiel zeigt, wie sich eine Anwendung für ein Activated-Ereignis registriert und damit umgeht. Wenn sie ein Activated-Ereignis empfängt, verwendet diese Anwendung die Ereignisargumente, um festzustellen, welche Art von Aktion die Aktivierung verursacht hat, und reagiert entsprechend darauf.

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);
    }
}

Umleitungslogik basierend auf der Aktivierungsart

In diesem Beispiel registriert die Anwendung einen Handler für das Ereignis Activated und prüft auch die Args des Aktivierungsereignisses, um zu entscheiden, ob die Aktivierung an eine andere Instanz umgeleitet werden soll.

Bei den meisten Aktivierungsarten fährt die App mit ihrem regulären Initialisierungsprozess fort. Wenn die Aktivierung jedoch durch das Öffnen eines zugehörigen Dateityps verursacht wurde und eine andere Instanz dieser Anwendung die Datei bereits geöffnet hat, leitet die aktuelle Instanz die Aktivierung an die vorhandene Instanz weiter und beendet sie.

Diese Anwendung verwendet die Schlüsselregistrierung, um festzustellen, welche Dateien in welchen Instanzen geöffnet sind. Wenn eine Instanz eine Datei öffnet, registriert sie einen Schlüssel, der diesen Dateinamen enthält. Andere Instanzen können dann die registrierten Schlüssel untersuchen und nach bestimmten Dateinamen suchen und sich selbst als Instanz dieser Datei registrieren, wenn dies noch keine andere Instanz getan hat.

Beachten Sie, dass die Schlüsselregistrierung selbst zwar Teil der App Lifecycle API im Windows App SDK ist, der Inhalt des Schlüssels aber nur in der App selbst angegeben wird. Eine Anwendung muss weder einen Dateinamen noch andere aussagekräftige Daten registrieren. Diese Anwendung hat sich jedoch aufgrund ihrer besonderen Bedürfnisse und der unterstützten Arbeitsabläufe dafür entschieden, offene Dateien über Schlüssel zu verfolgen.

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;
}

Beliebige Umleitung

Dieses Beispiel ist eine Erweiterung des vorherigen Beispiels, indem anspruchsvollere Umleitungsregeln hinzugefügt werden. Die Anwendung führt weiterhin die Prüfung auf offene Dateien aus dem vorherigen Beispiel durch. Während jedoch im vorherigen Beispiel immer eine neue Instanz erstellt wurde, wenn keine Umleitung aufgrund der Prüfung der offenen Datei erfolgte, wird in diesem Beispiel das Konzept einer „wiederverwendbaren“ Instanz hinzugefügt. Wenn eine wiederverwendbare Instanz gefunden wird, leitet die aktuelle Instanz zur wiederverwendbaren Instanz um und beendet sich. Andernfalls registriert es sich selbst als wiederverwendbar und fährt mit seiner normalen Initialisierung fort.

Auch hier ist zu beachten, dass das Konzept einer „wiederverwendbaren“ Instanz in der App-Lifecycle-API nicht existiert; sie wird nur innerhalb der App selbst erstellt und verwendet.

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;
}

Orchestrierung der Umleitung

In diesem Beispiel wird das Umleitungsverhalten weiter verfeinert. Hier kann sich eine App-Instanz als die Instanz registrieren, die alle Aktivierungen einer bestimmten Art bearbeitet. Wenn eine Instanz einer App eine Protocol-Aktivierung erhält, prüft sie zunächst, ob es eine Instanz gibt, die bereits für die Bearbeitung von Protocol-Aktivierungen registriert ist. Wenn es eine findet, leitet es die Aktivierung zu dieser Instanz um. Wenn nicht, registriert sich die aktuelle Instanz für Protocol-Aktivierungen und wendet dann zusätzliche Logik an (nicht gezeigt), die die Aktivierung aus einem anderen Grund umleiten kann.

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);
    }
}

Im Gegensatz zur UWP-Version von RedirectActivationTo erfordert die Implementierung von RedirectActivationToAsync des Windows App SDK die explizite Übergabe von Ereignisargumenten bei der Umleitung von Aktivierungen. Dies ist notwendig, denn während UWP Aktivierungen streng kontrolliert und sicherstellen kann, dass die richtigen Aktivierungsargumente an die richtigen Instanzen übergeben werden, unterstützt die Version des Windows App SDK viele Plattformen und kann sich nicht auf UWP-spezifische Funktionen verlassen. Ein Vorteil dieses Modells ist, dass Anwendungen, die das Windows App SDK verwenden, die Möglichkeit haben, die Argumente zu ändern oder zu ersetzen, die an die Zielinstanz übergeben werden.

Umleitung ohne Blockierung

Die meisten Anwendungen werden so früh wie möglich umleiten wollen, bevor sie unnötige Initialisierungsarbeit leisten. Bei einigen Anwendungstypen läuft die Initialisierungslogik in einem STA-Thread, der nicht blockiert werden darf. AppInstance.RedirectActivationToAsync-Methode ist asynchron und die aufrufende Anwendung muss warten, bis die Methode abgeschlossen ist, sonst schlägt die Umleitung fehl. Wenn Sie jedoch auf einen asynchronen Aufruf warten, wird die STA blockiert. Rufen Sie in diesen Fällen RedirectActivationToAsync in einem anderen Thread auf, und setzen Sie ein Ereignis, wenn der Aufruf abgeschlossen ist. Warten Sie dann auf dieses Ereignis mit nicht blockierenden APIs wie CoWaitForMultipleObjects. Hier ist ein C#-Beispiel für eine WPF-Anwendung.

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;
}

Abmeldung zur Weiterleitung

Apps, die einen Schlüssel registriert haben, können diesen Schlüssel jederzeit wieder abmelden. In diesem Beispiel wird davon ausgegangen, dass die aktuelle Instanz zuvor einen Schlüssel registriert hat, der angibt, dass sie eine bestimmte Datei geöffnet hat, was bedeutet, dass nachfolgende Versuche, diese Datei zu öffnen, zu ihr umgeleitet werden. Wenn diese Datei geschlossen wird, muss der Schlüssel, der den Dateinamen enthält, gelöscht werden.

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

Warnung

Obwohl die Registrierung von Schlüsseln automatisch aufgehoben wird, wenn ihr Prozess beendet wird, sind Wettlaufbedingungen möglich, bei denen eine andere Instanz eine Umleitung zu der beendeten Instanz initiiert haben kann, bevor die Registrierung der beendeten Instanz aufgehoben wurde. Um diese Möglichkeit abzuschwächen, kann eine Anwendung UnregisterKey verwenden, um die Registrierung ihres Schlüssels manuell aufzuheben, bevor sie beendet wird, so dass die Anwendung die Möglichkeit hat, Aktivierungen an eine andere Anwendung umzuleiten, die nicht gerade beendet wird.

Informationen zur Instanz

Die Klasse Microsoft.Windows.AppLifeycle.AppInstance stellt eine einzelne Instanz einer App dar. In der aktuellen Vorschau enthält AppInstance nur die Methoden und Eigenschaften, die zur Unterstützung der Aktivierungsumleitung erforderlich sind. In späteren Versionen wird AppInstance um weitere Methoden und Eigenschaften erweitert, die für eine App-Instanz relevant sind.

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());
    }
}