Instanciation d’application avec l’API de cycle de vie de l’application

Le modèle d’instanciation d’une application détermine si plusieurs instances du processus de votre application peuvent s’exécuter en même temps.

Prérequis

Pour utiliser l’API de cycle de vie des applications dans le Windows App SDK :

  1. Téléchargez et installez la dernière version du SDK d'application Windows. Pour plus d’informations, consultez Installer des outils pour le SDK d’application Windows.
  2. Suivez les instructions relatives à la création de votre premier projet WinUI 3 ou à l’utilisation du SDK d’application Windows dans un projet existant.

Applications à instance unique

Remarque

Pour un exemple de mise en œuvre de l’instanciation unique dans une application WinUI 3 avec C#, veuillez consulter l’article Rendre l’application à instance unique sur le blog des développeurs Windows.

Les applications sont à instance unique s’il ne peut y avoir qu’un seul processus principal en cours d’exécution à la fois. Tenter de lancer une deuxième instance d’une application à instance unique entraîne généralement l’activation de la première fenêtre principale de l’instance. Notez que cela ne s’applique qu’au processus principal. Les applications à instance unique peuvent créer plusieurs processus d’arrière-plan et être toujours considérées comme à instance unique.

Les applications UWP sont à instance unique par défaut. Elles ont cependant la capacité de devenir multi-instanciées en décidant au moment du lancement de créer une instance supplémentaire ou d’activer une instance existante à la place.

L’application Courrier de Windows est un bon exemple d’application à instance unique. Lorsque vous lancez Courrier pour la première fois, une nouvelle fenêtre sera créée. Si vous essayez de lancer Courrier à nouveau, la fenêtre Courrier existante sera activée à la place.

Applications multi-instanciées

Les applications sont multi-instanciées si le processus principal peut être exécuté plusieurs fois simultanément. Tenter de lancer une deuxième instance d’une application multi-instanciée crée un nouveau processus et une nouvelle fenêtre principale.

Traditionnellement, les applications déployées sont multi-instanciées par défaut, mais peuvent implémenter l’instanciation unique lorsque nécessaire. Cela se fait généralement en utilisant un unique mutex nommé pour indiquer si une application est déjà en cours d’exécution.

Le Bloc-notes est un bon exemple d’application multi-instanciée. Chaque fois que vous essayez de lancer le Bloc-notes, une nouvelle instance du Bloc-notes sera créée indépendamment du nombre d’instances déjà en cours d’exécution.

Différences entre l’instanciation de l’App SDK Windows et l’instanciation UWP

Le comportement d’instanciation dans l’App SDK Windows est basé sur le modèle de classe UWP, mais avec quelques différences clés :

Classe AppInstance

  • UWP : La classe Windows.ApplicationModel.AppInstance - est uniquement axée sur les scénarios de redirection d’instance.
  • App SDK Windows : La classe Microsoft.Windows.AppLifeycle.AppInstance prend en charge les scénarios de redirection d’instance et contient des fonctionnalités supplémentaires pour prendre en charge de nouvelles fonctionnalités dans les versions ultérieures.

Liste des instances

  • UWP :GetInstances ne renvoie que les instances pour lesquelles l’application s’est explicitement inscrite pour une redirection potentielle.
  • App SDK Windows :GetInstances renvoie toutes les instances en cours d’exécution de l’application qui utilisent l’API AppInstance, qu’elles aient ou non enregistré une clé. Cela peut inclure l’instance actuelle. Si vous souhaitez que l’instance actuelle soit incluse dans la liste, appelez AppInstance.GetCurrent. Des listes distinctes sont conservées pour différentes versions de la même application, ainsi que pour les instances d’applications lancées par différents utilisateurs.

Inscription des clés

Chaque instance d’une application multi-instanciée peut enregistrer une clé arbitraire via la méthode FindOrRegisterForKey. Les clés n’ont aucune signification inhérente ; les applications peuvent utiliser les clés de la manière qui leur convient.

Une instance d’une application peut définir sa clé à tout moment, mais une seule clé est autorisée pour chaque instance ; définir une nouvelle valeur écrase la valeur précédente.

Une instance d’une application ne peut pas définir sa clé sur la même valeur qu’une autre instance a déjà enregistrée. Tenter d’enregistrer une clé existante entraînera FindOrRegisterForKey retournant l’instance d’application qui a déjà enregistré cette clé.

  • UWP : Une instance doit enregistrer une clé pour être incluse dans la liste renvoyée par GetInstances.
  • App SDK Windows : L’enregistrement d’une clé est dissocié de la liste des instances. Une instance n’a pas besoin d’enregistrer une clé pour être incluse dans la liste.

Désinscription des clés

Une instance d’une application peut désenregistrer sa clé.

  • UWP : Lorsqu’une instance désenregistre sa clé, elle n’est plus disponible pour la redirection d’activation et n’est pas incluse dans la liste des instances renvoyée par GetInstances.
  • App SDK Windows : Une instance qui a désenregistré sa clé est toujours disponible pour la redirection d’activation et est toujours incluse dans la liste des instances renvoyée par GetInstances.

Cibles de redirection d’instance

Plusieurs instances d’une application peuvent s’activer mutuellement, un processus appelé « redirection d’activation ». Par exemple, une application peut implémenter l’instanciation unique en s’initialisant uniquement si aucune autre instance de l’application n’est trouvée au démarrage, et rediriger et quitter si une autre instance existe. Les applications multi-instanciées peuvent rediriger des activations lorsque cela est approprié selon la logique métier de cette application. Lorsqu’une activation est redirigée vers une autre instance, elle utilise le Activated rappel de la même manière que dans tous les autres scénarios d’activation.

  • UWP : Seules les instances ayant enregistré une clé peuvent être une cible de redirection.
  • App SDK Windows : Toute instance peut être une cible de redirection, qu’elle ait ou non une clé enregistrée.

Comportement après la redirection

  • UWP : La redirection est une opération terminale ; l’application est terminée après avoir redirigé l’activation, même si la redirection a échoué.

  • App SDK Windows : Dans l’App SDK Windows, la redirection n’est pas une opération terminale. Cela reflète en partie les problèmes potentiels de terminaison arbitraire d’une application Win32 qui a peut-être déjà alloué de la mémoire, mais permet également de prendre en charge des scénarios de redirection plus sophistiqués. Considérez une application multi-instanciée où une instance reçoit une demande d’activation tout en effectuant un travail intensif en CPU. Cette application peut rediriger la demande d’activation vers une autre instance et continuer son traitement. Ce scénario ne serait pas possible si l’application était terminée après la redirection.

Une demande d’activation peut être redirigée plusieurs fois. Instance A pourrait rediriger vers l’instance B, qui à son tour pourrait rediriger vers l’instance C. Les applications Windows App SDK qui tirent parti de cette fonctionnalité doivent se prémunir contre la redirection circulaire - si C redirige vers A dans l’exemple ci-dessus, il existe une boucle d’activation potentiellement infinie. Il revient à l’application de déterminer comment gérer la redirection circulaire en fonction de ce qui a du sens pour les flux de travail pris en charge par l’application.

Événements d’activation

Pour gérer la réactivation, l’application peut s’inscrire à un événement Activated.

Exemples

Gestion des activations

Cet exemple démontre comment une application s’inscrit et gère un événement Activated. Lorsqu’elle reçoit un événement Activated, cette application utilise les arguments de l’événement pour déterminer quel type d’action a provoqué l’activation, et réagit de manière appropriée.

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

Logique de redirection basée sur le type d’activation

Dans cet exemple, l’application enregistre un gestionnaire pour l’événement Activated, et vérifie également les arguments de l’événement d’activation pour décider s’il convient de rediriger l’activation vers une autre instance.

Pour la plupart des types d’activations, l’application continue son processus d’initialisation régulier. Cependant, si l’activation a été provoquée par l’ouverture d’un type de fichier associé, et si une autre instance de cette application a déjà ouvert le fichier, l’instance actuelle redirigera l’activation vers l’instance existante et se terminera.

Cette application utilise l’enregistrement de clé pour déterminer quels fichiers sont ouverts dans quelles instances. Lorsqu’une instance ouvre un fichier, elle enregistre une clé qui inclut ce nom de fichier. D’autres instances peuvent ensuite examiner les clés enregistrées et rechercher des noms de fichiers particuliers, et s’enregistrer elles-mêmes en tant qu’instance de ce fichier si aucune autre instance ne l’a déjà fait.

Notez que, bien que l’enregistrement de clé lui-même fasse partie de l’API de cycle de vie de l’application dans l’App SDK Windows, le contenu de la clé est spécifié uniquement dans l’application elle-même. Une application n’a pas besoin d’enregistrer un nom de fichier, ou toute autre donnée significative. Cette application, cependant, a décidé de suivre les fichiers ouverts via des clés en fonction de ses besoins particuliers et des flux de travail pris en charge.

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

Redirection arbitraire

Cet exemple élargit l’exemple précédent en ajoutant des règles de redirection plus sophistiquées. L’application effectue toujours la vérification des fichiers ouverts de l’exemple précédent. Cependant, là où l’exemple précédent créerait toujours une nouvelle instance s’il ne redirigeait pas en fonction de la vérification des fichiers ouverts, cet exemple ajoute le concept d’une instance « réutilisable ». Si une instance réutilisable est trouvée, l’instance actuelle redirige vers l’instance réutilisable et se termine. Sinon, elle s’enregistre en tant qu’instance réutilisable et continue avec son initialisation normale.

Encore une fois, notez que le concept d’une instance « réutilisable » n’existe pas dans l’API de cycle de vie de l’application ; il est créé et utilisé uniquement au sein de l’application elle-même.

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

Orchestration de la redirection

Cet exemple ajoute à nouveau un comportement de redirection plus sophistiqué. Ici, une instance d’application peut s’enregistrer en tant qu’instance qui gère toutes les activations d’un type spécifique. Lorsqu’une instance d’une application reçoit une activation Protocol, elle vérifie d’abord s’il existe une instance qui s’est déjà enregistrée pour gérer les activations Protocol. Si elle en trouve une, elle redirige l’activation vers cette instance. Sinon, l’instance actuelle s’enregistre pour les activations Protocol, puis applique une logique supplémentaire (non montrée) qui peut rediriger l’activation pour une autre raison.

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

Contrairement à la version UWP de RedirectActivationTo, l’implémentation de RedirectActivationToAsync de l’App SDK Windows nécessite de passer explicitement des arguments d’événement lors de la redirection des activations. Cela est nécessaire car alors que UWP contrôle étroitement les activations et peut garantir que les bons arguments d’activation sont transmis aux bonnes instances, la version de l’App SDK Windows prend en charge de nombreuses plates-formes et ne peut pas se fier à des fonctionnalités spécifiques à UWP. Un avantage de ce modèle est que les applications qui utilisent l’App SDK Windows ont la possibilité de modifier ou de remplacer les arguments qui seront transmis à l’instance cible.

Redirection sans blocage

La plupart des applications voudront rediriger le plus tôt possible, avant de faire un travail d’initialisation inutile. Pour certains types d’applications, la logique d’initialisation s’exécute sur un thread STA, qui ne doit pas être bloqué. La méthode AppInstance.RedirectActivationToAsync est asynchrone, et l’application appelante doit attendre que la méthode soit terminée, sinon la redirection échouera. Cependant, attendre un appel asynchrone bloquera le STA. Dans ces situations, appelez RedirectActivationToAsync dans un autre thread, et définissez un événement lorsque l’appel est terminé. Ensuite, attendez sur cet événement en utilisant des API non bloquantes telles que CoWaitForMultipleObjects. Voici un exemple C# pour une application WPF.

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

Désinscription de la redirection

Les applications qui ont enregistré une clé peuvent désenregistrer cette clé à tout moment. Cet exemple suppose que l’instance actuelle avait précédemment enregistré une clé indiquant qu’elle avait un fichier spécifique ouvert, ce qui signifie que les tentatives ultérieures d’ouvrir ce fichier seraient redirigées vers elle. Lorsque ce fichier est fermé, la clé qui contient le nom du fichier doit être supprimée.

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

Avertissement

Bien que les clés soient automatiquement désenregistrées lorsque leur processus se termine, des conditions de concurrence sont possibles où une autre instance peut avoir initié une redirection vers l’instance terminée avant que l’instance terminée ne soit désenregistrée. Pour atténuer cette possibilité, une application peut utiliser UnregisterKey pour désenregistrer manuellement sa clé avant d’être terminée, donnant ainsi à l’application une chance de rediriger les activations vers une autre application qui n’est pas en train de se terminer.

Informations sur les instances

La classe Microsoft.Windows.AppLifeycle.AppInstance représente une seule instance d’une application. Dans l’aperçu actuel, AppInstance inclut uniquement les méthodes et propriétés nécessaires pour prendre en charge la redirection de l’activation. Dans les versions ultérieures, AppInstance s’étendra pour inclure d’autres méthodes et propriétés pertinentes pour une instance d’application.

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