Inscrire votre application pour les lancements chauds accélérés

Cet article explique comment accélérer les lancements chauds pour les applications Windows à instance unique. Découvrez comment inscrire une instance de processus en cours d’exécution pour être averti directement lorsqu’un lancement se produit, ce qui évite la surcharge associée au flux de lancement complet. Cette approche réduit la latence globale en éliminant le processus intermédiaire qui unifie avec des instances déjà en cours d’exécution.

Flux classique aujourd’hui :

Capture d’écran du diagramme de flux montrant la voie de lancement à chaud normale avec un processus intermédiaire.

Avec un lancement accéléré :

Capture d’écran du diagramme de flux montrant la voie de lancement à chaud accélérée sans processus intermédiaire.

Cette fonctionnalité de lancement accélérée ne couvre pas chaque type de lancement, à partir de chaque application source, sur chaque version de Windows. Par conséquent, votre application doit conserver sa propre implémentation d’instanciation unique (consultez l’instanciation d’application avec l’API de cycle de vie de l’application pour une approche).

Le reste de cet article décrit la création d’une application WinUI 3 simple C++ qui prend en charge les lancements accélérés du menu Démarrer et du protocole.

Important

Les API de lancement accélérée font partie d’une fonctionnalité de Access limitée (voir classeLimitedAccessFeatures). Pour plus d’informations ou pour demander un jeton de déverrouillage, utilisez le LAF Access formulaire de demande de jeton.

Créer un projet

Dans Visual Studio, créez un projet. Pour cet exemple, dans la boîte de dialogue Créer une nouvelle boîte de dialogue project, définissez le filtre de langue sur C++ et le type de project sur WinUI, puis sélectionnez 'application videWinUI (empaquetée). Si vous ne voyez pas ce modèle, vérifiez que la charge de travail Dévelopment d'applicationsWinUI est installée dans le programme d'installation Visual Studio.

Implémenter l’instanciation unique

Vous pouvez utiliser n’importe quelle technique de votre choix pour implémenter l’instanciation unique au sein de votre application. Dans cet exemple, utilisez les API de cycle de vie des applications Windows App SDK.

void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
{
    AppInstance mainInstance = AppInstance::FindOrRegisterForKey(L"SingleInstance");
    if (mainInstance.IsCurrent())
    {
        // We're the main instance. Register to receive redirected launches.
        EventHandler<AppActivationArguments> handler(this, &App::OnRedirectedActivation);
        g_activatedRevoker = mainInstance.Activated(winrt::auto_revoke, handler);
        // Proceed with creating the main window.
        window = make<MainWindow>();
        window.Activate();

        window.SetMessage(L"Launched normally");
    }
    else
    {
        // We are a secondary instance. Redirect to the main instance and terminate when we're done.
        wil::unique_event redirectionComplete;
        redirectionComplete.create();

        auto redirectionOperation = mainInstance.RedirectActivationToAsync(mainInstance.GetActivatedEventArgs());
        redirectionOperation.Completed([&redirectionComplete](auto&&, auto&&)
        {
            redirectionComplete.SetEvent();
        });

        DWORD indexIgnored{};
        CoWaitForMultipleObjects(CWMO_DEFAULT, INFINITE, 1, redirectionComplete.addressof(), &indexIgnored);
        App::Exit();
    }
}

winrt::fire_and_forget App::OnRedirectedActivation(const IInspectable&, const AppActivationArguments& args)
{
    co_await wil::resume_foreground(window.DispatcherQueue());
    ::SwitchToThisWindow(GetWindowFromWindowId(window.AppWindow().Id()), TRUE);
    
    window.SetMessage(L"Launched via redirection");
}

Ajouter une association de protocole pour l’application

Pour recevoir des lancements de protocole accélérés, votre application doit être la cible par défaut d’une inscription de protocole. Dans cet exemple, utilisez un enregistrement préconfiguré. Pour plus d’informations sur l’inscription en tant que gestionnaire par défaut d’un schéma d’URI, consultez Gérer l’activation de l’URI.

Dans votre Package.appxmanifest, ajoutez une entrée pour une association de protocole :

<Applications>
  <Application Id="App" … />
    …
    <Extensions>
      <uap:Extension Category="windows.protocol">
        <uap:Protocol Name="acceleratedlaunchsample"/>
      </uap:Extension>
    </Extensions>
  </Application>
</Applications>

Votre application peut maintenant être lancée via sa vignette dans le menu Démarrer ou en tapant acceleratedlaunchsample: dans la boîte de dialogue Exécuter. Dans les deux cas, un processus intermédiaire se lance et redirige vers l’instance déjà en cours d’exécution.

S’inscrire aux lancements accélérés

À présent, l’application est prête à accepter les optimisations.

Les messages de lancement accélérés sont remis en tant que messages de fenêtre à un HWND au sein de votre processus. Vous indiquez au système quelle fenêtre doit recevoir les messages et les messages que vous souhaitez utiliser, avec l’interface IExperimentalAPIInvoker implémentée par CLSID_ExperimentalAPIInvoker.

IExperimentalAPIInvoker et CLSID_ExperimentalAPIInvoker sont définis comme suit :

[uuid("6fb3e48c-ba77-4f80-b9c9-77635a08a7f8")]
interface DECLSPEC_NOVTABLE IExperimentalAPIInvoker : public IUnknown
{
    IFACEMETHOD(IsSupported)(PCWSTR methodName, BOOL* isSupported) = 0;
    IFACEMETHOD(Invoke)(PCWSTR methodName, INamedPropertyStore* args, BOOL* isSupported) = 0;
};

DEFINE_GUID(CLSID_ExperimentalAPIInvoker, 0x81AF2611, 0xE262, 0x4090, 0xA1, 0x5B, 0xC0, 0x88, 0x95, 0xB1, 0xA0, 0xF0);

IExperimentalAPIInvoker vous permet d’appeler des méthodes par leur nom avec des arguments regroupés dans un magasin de propriétés nommé. Les méthodes de lancement accéléré sont les suivantes :

Méthode : « RegisterAcceleratedUriLaunch »

Inscrivez-vous aux lancements de protocole remis sous la forme d'une chaîne UTF-16 terminée par un caractère null (PCWSTR) via WM_COPYDATA pour la fenêtre cible (HWND).

Vous ne pouvez appeler cette méthode qu’une seule fois par IExperimentalAPIInvoker instance d’objet. Une fois que vous avez inscrit, ne relâchez pas l’objet tant que vous n’avez pas annulé l’inscription.

Nom du paramètre Type Descriptif
"targetWindow" VT_I8 HWND, converti en LONGLONG signé, qui reçoit un message de fenêtre quand votre application est lancée pour sa tuile principale dans le menu Démarrer ou d'autres emplacements.
"copyDataFormatId" VT_UI4 Valeur transmise en tant que COPYDATASTRUCT.dwData lors de la transmission de la chaîne URI.
"schemes" VT_LPWSTR \| VT_VECTOR Liste des schémas URI à enregistrer. Seuls ceux pour lesquels le processus appelant est le gestionnaire par défaut sont accélérés.

Méthode : « UnregisterAcceleratedUriLaunch »

Annuler l'inscription des lancements URI. Aucun argument. Doit être appelé sur le même objet que celui avec lequel il a été précédemment enregistré.

Méthode : « RegisterAcceleratedTileLaunch »

Inscrivez-vous aux notifications de lancement de tuiles transmises par un message de fenêtre sélectionné. Vous ne pouvez appeler cette méthode qu’une seule fois pour chaque IExperimentalAPIInvoker instance d’objet. Une fois que vous avez inscrit, ne relâchez pas l’objet tant que vous n’avez pas annulé l’inscription.

Nom du paramètre Type Descriptif
"targetWindow" VT_I8 HWND, converti en LONGLONG signé, qui reçoit un message de fenêtre quand votre application est lancée pour sa tuile principale dans le menu Démarrer ou d'autres emplacements.
"messageId" VT_UI4 ID de message de la fenêtre qui est envoyé à la fenêtre cible spécifiée.

Méthode : « UnregisterAcceleratedTileLaunch »

Se désinscrire des lancements de tuiles. Cette méthode ne prend aucun argument. Vous devez l’appeler sur le même objet que celui que vous avez enregistré précédemment.

Mettez-le ensemble

Pour simplifier les étapes précédentes, en particulier pour les applications qui n'ont pas d'accès facile à une procédure de fenêtre existante, cet exemple fournit un assistant autonome qui gère une fenêtre de travail et l'enregistre pour les lancements accélérés. Vous devez créer cet objet sur un thread avec une pompe de messages active (par exemple, le thread d’interface utilisateur d’une application WinUI).

Tout d’abord, copiez le code suivant dans un fichier d’en-tête dans votre project :

#pragma once
#include <propsys.h>
#include <propvarutil.h>
#include <initguid.h>
#include <vector>
#include <functional>
#include <wil/result.h>
#include <wil/resource.h>

[uuid("6fb3e48c-ba77-4f80-b9c9-77635a08a7f8")]
interface DECLSPEC_NOVTABLE IExperimentalAPIInvoker : public IUnknown
{
    IFACEMETHOD(IsSupported)(PCWSTR methodName, BOOL* isSupported) = 0;
    IFACEMETHOD(Invoke)(PCWSTR methodName, INamedPropertyStore* args, BOOL* isSupported) = 0;
};

DEFINE_GUID(CLSID_ExperimentalAPIInvoker, 0x81AF2611, 0xE262, 0x4090, 0xA1, 0x5B, 0xC0, 0x88, 0x95, 0xB1, 0xA0, 0xF0);

class AcceleratedLaunchHelper
{
public:
    ~AcceleratedLaunchHelper()
    {
        if (m_hwnd != nullptr)
        {
            FAIL_FAST_IF(::GetWindowThreadProcessId(m_hwnd, nullptr) != ::GetCurrentThreadId());

            if (m_tileLaunchCallback)
            {
                BOOL isSupported;
                m_invoker->Invoke(L"UnregisterAcceleratedTileLaunch", nullptr, &isSupported);
            }

            if (m_uriLaunchCallback)
            {
                BOOL isSupported;
                m_invoker->Invoke(L"UnregisterAcceleratedUriLaunch", nullptr, &isSupported);
            }

            ::DestroyWindow(m_hwnd);
        }
    }

    inline bool TryRegisterForUris(
        const std::vector<PCWSTR>& uris, std::function<void(PCWSTR)> callback)
    {
        EnsureWorkerWindow();

        if (!m_invoker)
        {
            return false;
        }

        winrt::com_ptr<INamedPropertyStore> args;
        THROW_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&args)));

        wil::unique_prop_variant targetWindowParam;
        THROW_IF_FAILED(InitPropVariantFromInt64(reinterpret_cast<LONGLONG>(m_hwnd), targetWindowParam.reset_and_addressof()));
        args->SetNamedValue(L"targetWindow", targetWindowParam);

        wil::unique_prop_variant copyDataFormatIdParam;
        THROW_IF_FAILED(InitPropVariantFromUInt32(0,
            copyDataFormatIdParam.reset_and_addressof()));
        args->SetNamedValue(L"copyDataFormatId", copyDataFormatIdParam);

        wil::unique_prop_variant schemesParam;
        THROW_IF_FAILED(InitPropVariantFromStringVector(
            const_cast<PCWSTR*>(uris.data()), uris.size(), schemesParam.reset_and_addressof()));
        THROW_IF_FAILED(args->SetNamedValue(L"schemes", schemesParam));

        BOOL isSupported{ FALSE };
        THROW_IF_FAILED(m_invoker->Invoke(L"RegisterAcceleratedUriLaunch", args.get(),
            &isSupported));

        if (isSupported)
        {
            m_uriLaunchCallback = callback;
            m_uriSchemes = uris;
        }

        return isSupported;
    }

    inline bool TryRegisterForTileLaunch(std::function<void()> callback)
    {
        EnsureWorkerWindow();

        if (!m_invoker)
        {
            return false;
        }

        winrt::com_ptr<INamedPropertyStore> args;
        THROW_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&args)));

        wil::unique_prop_variant targetWindowParam;
        THROW_IF_FAILED(InitPropVariantFromInt64(reinterpret_cast<LONGLONG>(m_hwnd), targetWindowParam.reset_and_addressof()));
        args->SetNamedValue(L"targetWindow", targetWindowParam);

        wil::unique_prop_variant tileLaunchMessageIdParam;
        THROW_IF_FAILED(InitPropVariantFromUInt32(WM_USER,
            tileLaunchMessageIdParam.reset_and_addressof()));
        THROW_IF_FAILED(args->SetNamedValue(L"messageId", tileLaunchMessageIdParam));

        BOOL isSupported{ FALSE };
        THROW_IF_FAILED(m_invoker->Invoke(L"RegisterAcceleratedTileLaunch", args.get(),
            &isSupported));
    
        if (isSupported)
        {
            m_tileLaunchCallback = callback;
        }

        return isSupported;
    }

private:
    static inline LRESULT s_WorkerWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_NCCREATE:
        {
            auto pThis = reinterpret_cast<AcceleratedLaunchHelper*>(
                (reinterpret_cast<CREATESTRUCT*>(lParam))->lpCreateParams);
            if (pThis)
            {
                ::SetWindowLongPtr(hwnd, 0, reinterpret_cast<LONG_PTR>(pThis));
            }
            break;
        }
        }

        auto pThis = reinterpret_cast<AcceleratedLaunchHelper*>(GetWindowLongPtr(hwnd, 0));
        if (pThis)
        {
            return pThis->WorkerWndProc(hwnd, msg, wParam, lParam);
        }

        return 0;
    }

    inline LRESULT WorkerWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_COPYDATA:
        {
            const auto copyData = reinterpret_cast<COPYDATASTRUCT*>(lParam);
            if (copyData->dwData == 0)
            {
                const auto uri = reinterpret_cast<PCWSTR>(copyData->lpData);
                if (m_uriLaunchCallback)
                {
                    m_uriLaunchCallback(uri);
                }
            }
        }
        break;

        case WM_USER:
            if (m_tileLaunchCallback)
            {
                m_tileLaunchCallback();
            }
            break;
        }
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    inline void EnsureWorkerWindow()
    {
        if (m_hwnd != nullptr)
        {
            return;
        }

        // Best-effort: Try to acquire the IExperimentalAPIInvoker.
        // If we can't, the OS does not currently support the feature.
        if (FAILED(CoCreateInstance(CLSID_ExperimentalAPIInvoker, nullptr, CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_invoker)))
        {
            return;
        }

        WNDCLASSEX wcex{ sizeof(wcex) };
        wcex.lpszClassName = L"AcceleratedLaunchWorkerClass";
        wcex.cbWndExtra = sizeof(this);
        wcex.lpfnWndProc = s_WorkerWndProc;
        wcex.hInstance = GetModuleHandle(nullptr);
        ::RegisterClassEx(&wcex);

        m_hwnd = ::CreateWindowEx(0, wcex.lpszClassName, nullptr, 0, 0, 0, 0, 0,
            HWND_MESSAGE, 0, wcex.hInstance, this);

        FAIL_FAST_LAST_ERROR_IF_NULL(m_hwnd);
    }

    // Worker window for receiving notifications
    HWND m_hwnd{ nullptr };

    // Entry point for registration & unregistration
    winrt::com_ptr<IExperimentalAPIInvoker> m_invoker;

    // Callbacks
    std::function<void()> m_tileLaunchCallback;
    std::function<void(PCWSTR)> m_uriLaunchCallback;

    // Registered URI schemes
    std::vector<PCWSTR> m_uriSchemes;
};

Vous pouvez maintenant intégrer ce code à votre application :

void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
{
    …
    if (mainInstance.IsCurrent())
    {
        …

        m_acceleratedLaunchHelper = std::make_unique<AcceleratedLaunchHelper>();
        m_acceleratedLaunchHelper->TryRegisterForTileLaunch(
            [this]() { OnAcceleratedTileLaunch(); });

        std::vector<PCWSTR> schemes = { L"acceleratedlaunchsample" };
        m_acceleratedLaunchHelper->TryRegisterForUris(schemes,
            [this](PCWSTR uri) { OnAcceleratedUriLaunch(uri); });
    }
    …
}

void App::OnAcceleratedTileLaunch()
{
    ::SwitchToThisWindow(GetWindowFromWindowId(window.AppWindow().Id()), TRUE);

    window.SetMessage(L"Launched via accelerated tile launch.");
}

void App::OnAcceleratedUriLaunch(PCWSTR uri)
{
    ::SwitchToThisWindow(GetWindowFromWindowId(window.AppWindow().Id()), TRUE);

    std::wstring message = L"Launched via accelerated URI launch: ";
    message += uri;
    window.SetMessage(message);
}

Avec ce code, votre application bénéficie des lancements accélérés sur les versions du système d’exploitation qui prennent en charge cette fonctionnalité.

Gérer l’activation de l’URI

Instanciation d'une application avec l'API de cycle de vie applicatif

API de cycle de vie des applications Windows App SDK