Registra tu aplicación para lanzamientos cálidos acelerados

En este artículo se describe cómo acelerar los lanzamientos en caliente para aplicaciones de Windows de instancia única. Obtenga información sobre cómo registrar una instancia de proceso en ejecución para recibir una notificación directa cuando se produce un inicio, evitando la sobrecarga asociada al flujo de inicio completo. Este enfoque reduce la latencia general eliminando el proceso intermediario que unifica con instancias ya en ejecución.

Flujo típico hoy:

Captura de pantalla del diagrama de flujo que muestra la ruta normal de inicio en caliente con un proceso intermedio.

Con un lanzamiento acelerado:

Captura de pantalla del diagrama de flujo que muestra la ruta de inicio rápido acelerado sin proceso intermediario.

Esta funcionalidad de inicio acelerado no cubre todo tipo de inicio, desde todas las aplicaciones de origen, en cada versión de Windows. Como resultado, su aplicación todavía debe mantener su propia implementación de instancias únicas; para un enfoque, consulte la creación de instancias de aplicaciones con la API de ciclo de vida de la aplicación.

El resto de este artículo explica cómo crear una sencilla aplicación WinUI 3 de C++ que admite lanzamientos acelerados del Menú de Inicio y de protocolos.

Importante

Las APIs de inicio acelerado forman parte de una función de acceso limitado (consulte LimitedAccessFeatures class). Para obtener más información o solicitar un token de desbloqueo, use el LAF Access formulario de solicitud de token.

Crear un nuevo project

En Visual Studio, cree un nuevo project. En este ejemplo, en el cuadro de diálogo Crear una nueva project, establezca el filtro de lenguaje en C++ y el tipo de project en WinUI y, a continuación, seleccione WinUI Blank App (Empaquetado). Si no ve esta plantilla, asegúrese de que tiene instalada la carga de trabajo WinUI en el instalador de Visual Studio.

Implementación de instancias únicas

Puedes usar cualquier técnica que prefieras para implementar la creación de instancias únicas dentro de la aplicación. En este ejemplo, use las API de ciclo de vida de aplicaciones de 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");
}

Adición de una asociación de protocolo para la aplicación

Para recibir inicios de protocolo acelerado, la aplicación debe ser el destino predeterminado de un registro de protocolo. En este ejemplo, use un registro empaquetado. Para obtener más información sobre cómo registrarse como controlador predeterminado para un esquema de URI, vea Controlar la activación de URI.

En su Package.appxmanifest, agregue una entrada para una asociación de protocolo.

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

La aplicación ahora se puede iniciar a través de su icono en el menú Inicio o escribiendo acceleratedlaunchsample: en el cuadro de diálogo Ejecutar. En ambos casos, un proceso intermediario inicia y redirige a la instancia ya en ejecución.

Registro para lanzamientos acelerados

Ahora la aplicación está lista para participar en las optimizaciones.

Los mensajes de inicio acelerado se entregan como mensajes de ventana a un HWND dentro de tu proceso. Indique al sistema qué ventana debe recibir los mensajes y qué mensajes desea usar, con la interfaz IExperimentalAPIInvoker que implementa CLSID_ExperimentalAPIInvoker.

IExperimentalAPIInvoker y CLSID_ExperimentalAPIInvoker se definen de la siguiente manera:

[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 permite invocar métodos por nombre, con argumentos empaquetados en un almacenamiento de propiedades nombrado. Los métodos para el inicio acelerado son:

Método: "RegisterAcceleratedUriLaunch"

Registre las aperturas de protocolo entregadas como una cadena UTF-16 terminada en un carácter nulo (PCWSTR) a través de WM_COPYDATA al HWND de destino.

Puede llamar a este método solo una vez por IExperimentalAPIInvoker instancia de objeto. Una vez registrado, no libere el objeto hasta después de anular el registro.

Nombre del parámetro Tipo Description
"targetWindow" VT_I8 El HWND, convertido a un LONGLONG firmado, que recibe un mensaje de ventana cuando la aplicación se inicia para su icono principal en el menú Inicio u otras ubicaciones.
"copyDataFormatId" VT_UI4 Valor que se pasa como COPYDATASTRUCT.dwData al entregar la cadena de URI.
"schemes" VT_LPWSTR \| VT_VECTOR Lista de esquemas de URI para registrarse. Solo se aceleran aquellos para los que el proceso que invoca actúa como controlador predeterminado.

Método: "UnregisterAcceleratedUriLaunch"

Cancelar el registro de los lanzamientos de URI. No hay argumentos. Debe llamarse en el mismo objeto con el que se registró previamente.

Método: "RegisterAcceleratedTileLaunch"

Regístrese para los lanzamientos de mosaicos a través de un mensaje de ventana seleccionado. Puede llamar a este método solo una vez para cada IExperimentalAPIInvoker instancia de objeto. Después de registrarse, no libere el objeto hasta que se anule el registro.

Nombre del parámetro Tipo Description
"targetWindow" VT_I8 El HWND, convertido a un LONGLONG firmado, que recibe un mensaje de ventana cuando la aplicación se inicia para su icono principal en el menú Inicio u otras ubicaciones.
"messageId" VT_UI4 Identificador de mensaje de ventana que va a la ventana de destino especificada.

Método: "UnregisterAcceleratedTileLaunch"

Anule el registro de los inicios del icono. Este método no toma ningún argumento. Debe llamarlo en el mismo objeto con el que se registró anteriormente.

Reunirlo

Para simplificar los pasos anteriores, especialmente para las aplicaciones que no tienen fácil acceso a un procedimiento de ventana existente, este ejemplo proporciona un helper autónomo que administra una ventana de operación y la registra para lanzamientos acelerados. Debe crear este objeto en un subproceso con una bomba de mensajes activa (como el subproceso de interfaz de usuario de una aplicación WinUI).

En primer lugar, copie el código siguiente en un archivo de encabezado en el 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;
};

Ahora puede integrar este código en la aplicación:

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

Con este código, la aplicación se beneficia de los inicios acelerados en versiones del sistema operativo que admiten esta característica.

Gestionar la activación de URI

Creación de instancias de aplicaciones con la API del ciclo de vida de la aplicación

API de ciclo de vida de aplicaciones de Windows App SDK