Registre seu aplicativo para inicializações rápidas aceleradas

Este artigo descreve como acelerar inicializações quentes para aplicativos windows de instância única. Saiba como registrar uma instância de processo em execução para ser notificada diretamente quando ocorrer uma inicialização, evitando sobrecarga associada ao fluxo de inicialização completo. Essa abordagem reduz a latência geral eliminando o processo intermediário que unifica com instâncias já em execução.

Fluxo típico hoje:

Captura de tela do diagrama de fluxo mostrando o caminho normal de inicialização quente com o processo intermediário.

Com uma inicialização acelerada:

Captura de tela do diagrama de fluxo mostrando o caminho de inicialização quente acelerada sem o processo intermediário.

Essa funcionalidade de inicialização acelerada não abrange todos os tipos de inicialização, de todos os aplicativos de origem, em todas as versões do Windows. Como resultado, seu aplicativo ainda deve manter sua própria implementação de instanciação única (consulte a instanciação de aplicativo com a API de ciclo de vida do aplicativo para uma abordagem).

O restante deste artigo explica como criar um aplicativo C++ WinUI 3 simples que oferece o apoio a inicializações aceleradas do Menu Iniciar e do protocolo.

Importante

As APIs de lançamento acelerado fazem parte de um recurso de Acesso Limitado (consulte a LimitedAccessFeatures class). Para obter mais informações ou solicitar um token de desbloqueio, use o LAF Access Formulário de Solicitação de Token.

Criar um novo project

Em Visual Studio, crie um novo project. Para este exemplo, na caixa de diálogo Criar um novo projeto, defina o filtro de idioma como C++ e o tipo de projeto como WinUI, e em seguida selecione Aplicativo em Branco WinUI (Empacotado). Se você não vir esse modelo, verifique se tem a carga de trabalho de desenvolvimento de aplicações WinUI instalada no Instalador do Visual Studio.

Implementar instanciação única

Você pode usar qualquer técnica de sua escolha para implementar a instância única no seu aplicativo. Neste exemplo, use as APIs de Ciclo de Vida do Aplicativo do 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");
}

Adicionar uma associação de protocolo para o aplicativo

Para receber inicializações aceleradas de protocolo, seu aplicativo deve ser o destino padrão de um registro de protocolo. Neste exemplo, use um registro empacotado. Para obter mais informações sobre como se registrar como o manipulador padrão para um esquema de URI, consulte Manipular ativação de URI.

Em seu Package.appxmanifest, adicione uma entrada para uma associação de protocolo:

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

Seu aplicativo agora pode ser iniciado por meio de seu bloco no Menu Iniciar ou digitando acceleratedlaunchsample: na caixa de diálogo Executar. Em ambos os casos, um processo intermediário é inicializado e redireciona para a instância que já está em execução.

Registre-se para lançamentos acelerados

Agora, o aplicativo está pronto para aceitar as otimizações.

As mensagens de inicialização aceleradas são entregues como mensagens de janela para um HWND em seu processo. Você informa ao sistema qual janela deve receber as mensagens e quais mensagens deseja usar, com a interface IExperimentalAPIInvoker que é implementada por CLSID_ExperimentalAPIInvoker.

IExperimentalAPIInvoker e CLSID_ExperimentalAPIInvoker são definidos da seguinte maneira:

[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 que você chame métodos por nome com argumentos empacotados em um repositório de propriedades nomeado. Os métodos para inicialização acelerada são:

Método: "RegisterAcceleratedUriLaunch"

Registre-se para inicializações de protocolo entregues como uma PCWSTR (cadeia de caracteres UTF-16) terminada em nulo por meio de WM_COPYDATA para o HWND de destino.

Você pode chamar esse método apenas uma vez por IExperimentalAPIInvoker instância de objeto. Depois de se registrar, não libere o objeto até que você cancele o registro.

Nome do parâmetro Tipo Description
"targetWindow" VT_I8 O HWND, convertido em um LONGLONG assinado, que recebe uma mensagem de janela quando seu aplicativo é iniciado para seu bloco primário no Menu Iniciar ou em outros locais.
"copyDataFormatId" VT_UI4 Valor passado como COPYDATASTRUCT.dwData ao entregar a string URI.
"schemes" VT_LPWSTR \| VT_VECTOR Lista de esquemas de URI para os quais registrar. Somente aqueles para os quais o processo de chamada é o manipulador padrão são acelerados.

Método: "UnregisterAcceleratedUriLaunch"

Cancelar o registro de inicializações de URI. Sem argumentos. Deve ser chamado no mesmo objeto com o qual foi registrado anteriormente.

Método: "RegisterAcceleratedTileLaunch"

Registre-se para receber lançamentos de tiles entregues por uma mensagem de janela escolhida. Você pode chamar esse método apenas uma vez para cada IExperimentalAPIInvoker instância de objeto. Depois de se registrar, não libere o objeto até cancelar o registro.

Nome do parâmetro Tipo Description
"targetWindow" VT_I8 O HWND, convertido em um LONGLONG assinado, que recebe uma mensagem de janela quando seu aplicativo é iniciado para seu bloco primário no Menu Iniciar ou em outros locais.
"messageId" VT_UI4 A ID da mensagem da janela que vai para a janela de destino especificada.

Método: "UnregisterAcceleratedTileLaunch"

Cancelar o registro de inicializações de bloco. Esse método não usa argumentos. Você deve chamá-lo no mesmo objeto com o qual você registrou anteriormente.

Juntando tudo

Para simplificar as etapas anteriores, especialmente para aplicativos que não têm acesso fácil ao procedimento de janela existente, este exemplo fornece um auxiliar independente que gerencia uma janela de trabalho dedicada e a registra para inicializações aceleradas. Você deve criar esse objeto em um thread com uma bomba de mensagem ativa (como o thread de interface do usuário de um aplicativo WinUI).

Primeiro, copie o seguinte código em um arquivo de cabeçalho em seu 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;
};

Agora você pode integrar esse código ao seu aplicativo:

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

Com esse código, seu aplicativo se beneficia de inicializações aceleradas em versões do sistema operacional que dão suporte a esse recurso.

Gerenciar a ativação de URI

Instanciação de aplicativo com a API de ciclo de vida do aplicativo

APIs do ciclo de vida do aplicativo Windows App SDK