Registrieren Sie Ihre App für schnellere Warmstarts

In diesem Artikel wird beschrieben, wie Warmstarts für Einzelinstanzen-Windows-Anwendungen beschleunigt werden. Erfahren Sie, wie Sie eine ausgeführte Prozessinstanz registrieren, um direkt benachrichtigt zu werden, wenn ein Start auftritt, wodurch der Aufwand vermieden wird, der dem vollständigen Startablauf zugeordnet ist. Dieser Ansatz reduziert die Gesamtlatenz, indem der zwischengeschaltete Prozess eliminiert wird, der mit bereits ausgeführten Instanzen vereinheitlicht wird.

Typischer Fluss heute:

Screenshot des Flussdiagramms, das den normalen Warmstartpfad mit intermediärem Prozess zeigt.

Mit einem beschleunigten Start:

Screenshot des Flussdiagramms mit beschleunigtem Warmstartpfad ohne zwischengeschalteten Prozess.

Diese beschleunigte Startfunktion deckt nicht jede Art von Start ab, von jeder Quellanwendung, auf jeder Windows-Version. Daher muss Ihre Anwendung weiterhin eine eigene Implementierung der Einzelinstanzierung beibehalten (siehe App-Instanzierung mit der App-Lebenszyklus-API für einen Ansatz).

Der Rest dieses Artikels führt Sie durch die Erstellung einer einfachen C++-WinUI 3-App, die beschleunigte Startmenü- und Protokollstarts unterstützt.

Von Bedeutung

Die apIs für den beschleunigten Start sind Teil eines Eingeschränkten Access-Features (siehe LimitedAccessFeatures-Klasse). Verwenden Sie für weitere Informationen oder zum Anfordern eines Entsperrtokens das LAF-Access Tokenanforderungsformular.

Neues Projekt erstellen

Erstellen Sie in Visual Studio ein neues Projekt. Legen Sie in diesem Beispiel im Dialogfeld Neues Projekt den Sprachfilter auf C++ und den Projekttyp auf WinUI fest, und wählen Sie dann WinUI Blank App (Verpackt) aus. Wenn diese Vorlage nicht angezeigt wird, stellen Sie sicher, dass die WinUI-Anwendungsentwicklung Workload im Visual Studio Installer installiert ist.

Single-Instanz-Implementierung

Sie können jede Methode Ihrer Wahl verwenden, um die Einzelinstanzerstellung in Ihrer App zu implementieren. Verwenden Sie in diesem Beispiel die App-Lifecycle-APIs des 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");
}

Hinzufügen einer Protokollzuordnung für die App

Um beschleunigte Protokollstarts zu erhalten, muss Ihre App das Standardziel einer Protokollregistrierung sein. Verwenden Sie in diesem Beispiel eine verpackte Registrierung. Weitere Informationen zum Registrieren als Standardhandler für ein URI-Schema finden Sie unter Behandeln der URI-Aktivierung.

Fügen Sie in Ihrem Package.appxmanifest einen Eintrag für eine Protokollzuordnung hinzu.

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

Ihre App kann jetzt über die Kachel im Startmenü oder durch Eingabe acceleratedlaunchsample: in das Dialogfeld "Ausführen" gestartet werden. In beiden Fällen startet ein zwischengeschalteter Prozess und leitet auf die bereits laufende Instanz um.

Registrieren für beschleunigte Starts

Jetzt ist die App bereit, sich für die Optimierungen zu entscheiden.

Die beschleunigten Startnachrichten werden in Ihrem Prozess als Fensternachrichten an einen HWND übermittelt. Sie teilen dem System mit, welches Fenster die Nachrichten empfangen soll und welche Nachrichten Sie verwenden möchten, indem Sie die Schnittstelle IExperimentalAPIInvoker verwenden, die von CLSID_ExperimentalAPIInvoker implementiert wird.

IExperimentalAPIInvoker und CLSID_ExperimentalAPIInvoker werden wie folgt definiert:

[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 ermöglicht es Ihnen, Methoden nach Namen mit Argumenten aufzurufen, die in einem benannten Eigenschaftsspeicher verpackt sind. Die Methoden für den beschleunigten Start sind:

Methode: "RegisterAcceleratedUriLaunch"

Registrieren Sie sich für Protokollstarts, die als nullterminierte UTF-16-Zeichenfolge (PCWSTR) über WM_COPYDATA an das Ziel-HWND übermittelt werden. Das WPARAM dieser Nachricht ist die Prozess-ID des Aufrufers, falls verfügbar.

Sie können diese Methode nur einmal pro IExperimentalAPIInvoker Objektinstanz aufrufen. Nachdem Sie sich registriert haben, lassen Sie das Objekt erst wieder los, nachdem Sie die Registrierung aufgehoben haben.

Parametername Typ Description
"targetWindow" VT_I8 Der HWND wird in ein signiertes LONGLONG-Element umgeschrieben, das eine WM_COPYDATA Fenstermeldung empfängt, wenn Die App für ein registriertes URI-Schema gestartet wird.
"copyDataFormatId" VT_UI4 Wert, der beim Bereitstellen der URI-Zeichenfolge als COPYDATASTRUCT.dwData übergeben wird.
"schemes" VT_LPWSTR \| VT_VECTOR Liste der zu registrierenden URI-Schemas. Nur diejenigen, für die der aufrufende Prozess der Standardhandler ist, werden beschleunigt.

Methode: "UnregisterAcceleratedUriLaunch"

Heben Sie die Registrierung von URI-Starts auf. Keine Argumente. Muss auf dasselbe Objekt angewendet werden, mit dem es zuvor registriert wurde.

Methode: "RegisterAcceleratedTileLaunch"

Registrieren Sie sich für Startvorgänge über Kacheln, die von einer ausgewählten Fensternachricht übermittelt werden. Das WPARAM dieser Nachricht ist die Prozess-ID des Aufrufers, falls verfügbar.

Sie können diese Methode nur einmal für jede IExperimentalAPIInvoker Objektinstanz aufrufen. Nachdem Sie sich registriert haben, lassen Sie das Objekt erst los, wenn Sie die Registrierung aufheben.

Parametername Typ Description
"targetWindow" VT_I8 Der HWND wird in ein signiertes LONGLONG-Element umgeschrieben, das eine Fenstermeldung empfängt, wenn die App für die primäre Kachel im Startmenü oder in der Taskleiste gestartet wird.
"messageId" VT_UI4 Die Fensternachrichten-ID, die zum angegebenen Zielfenster geht.

Methode: "UnregisterAcceleratedTileLaunch"

Heben Sie die Registrierung für Startvorgänge über Kacheln auf. Diese Methode akzeptiert keine Argumente. Sie müssen es für dasselbe Objekt aufrufen, bei dem Sie zuvor registriert haben.

Gesamtbild

Um die vorstehenden Schritte zu vereinfachen, insbesondere für Apps, die keinen einfachen Zugriff auf ein vorhandenes Fensterverfahren haben, stellt dieses Beispiel ein eigenständiges Hilfsprogramm bereit, das ein Arbeitsfenster verwaltet und für schnelle Starts registriert. Sie müssen dieses Objekt in einem Thread mit einer aktiven Nachrichtenpumpe erstellen (z. B. den UI-Thread einer WinUI-App).

Kopieren Sie zuerst den folgenden Code in eine Headerdatei in Ihrem 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;
};

Jetzt können Sie diesen Code in Ihre App integrieren:

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

Mit diesem Code profitiert Ihre App von beschleunigten Starts auf Betriebssystemversionen, die dieses Feature unterstützen.

URI-Aktivierung bearbeiten

App-Instanzierung mit der App-Lebenszyklus-API

Windows App SDK App-Lifecycle-APIs