Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo descrive come accelerare gli avviamenti rapidi per le applicazioni Windows a istanza unica. Informazioni su come registrare un'istanza del processo in esecuzione per ricevere una notifica diretta quando si verifica un avvio, evitando sovraccarichi associati al flusso di avvio completo. Questo approccio riduce la latenza complessiva eliminando il processo intermedio che unifica con istanze già in esecuzione.
Flusso tipico oggi:
Con un lancio accelerato:
Questa funzionalità di avvio accelerato non copre ogni tipo di avvio, da ogni applicazione di origine, in ogni versione di Windows. Di conseguenza, l'applicazione deve comunque mantenere la propria implementazione di un'istanza singola (vedere Istanze dell'app con l'API del ciclo di vita dell'app per un approccio).
Il resto di questo articolo illustra come creare una semplice app C++ con WinUI 3 che supporta l'avvio accelerato del menu Start e dei lanci di protocollo.
Importante
Le API di avvio accelerato fanno parte di una funzionalità di Access limitata (vedere la classe LimitedAccessFeatures). Per altre informazioni o per richiedere un token di sblocco, usare LAF Access Token Request Form.
Creare un nuovo project
In Visual Studio creare un nuovo project. Per questo esempio, nella finestra di dialogo Creare una nuova finestra di dialogo project impostare il filtro del linguaggio su C++ e il tipo di project su WinUI, quindi selezionare WinUI Blank App (Packaged). Se questo modello non viene visualizzato, assicurarsi di disporre del carico di lavoro WinUI installato nel programma di installazione di Visual Studio.
Implementare la creazione di istanze singole
Puoi usare qualsiasi tecnica di tua scelta per implementare il single instancing all'interno dell'app. In questo esempio usare le API del ciclo di vita delle app di 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");
}
Aggiungere un'associazione di protocollo per l'app
Per ricevere l'avvio del protocollo accelerato, l'app deve essere la destinazione predefinita di una registrazione del protocollo. In questo esempio usare una registrazione in pacchetto. Per altre informazioni su come eseguire la registrazione come gestore predefinito per uno schema URI, vedere Gestire l'attivazione URI.
Nel tuo Package.appxmanifest, aggiungi una voce per un'associazione di protocollo:
<Applications>
<Application Id="App" … />
…
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="acceleratedlaunchsample"/>
</uap:Extension>
</Extensions>
</Application>
</Applications>
L'app può ora essere avviata tramite il riquadro nel menu Start o digitando acceleratedlaunchsample: nella finestra di dialogo Esegui. In entrambi i casi, un processo intermedio avvia e reindirizza all'istanza già in esecuzione.
Registrarsi per l'avvio accelerato
Ora l'app è pronta per acconsentire esplicitamente alle ottimizzazioni.
I messaggi di avvio accelerato vengono recapitati come messaggi di finestra a un HWND all'interno del proprio processo. Indicare al sistema quale finestra deve ricevere i messaggi e quali messaggi si desidera usare, con l'interfaccia IExperimentalAPIInvoker implementata da CLSID_ExperimentalAPIInvoker.
IExperimentalAPIInvoker e CLSID_ExperimentalAPIInvoker sono definiti come segue:
[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 consente di chiamare i metodi in base al nome con argomenti inseriti in un archivio di proprietà denominato. I metodi per l'avvio accelerato sono:
Metodo: "RegisterAcceleratedUriLaunch"
Eseguire la registrazione per l'avvio del protocollo recapitato come stringa UTF-16 con terminazione Null (PCWSTR) tramite WM_COPYDATA all'HWND di destinazione.
È possibile chiamare questo metodo una sola volta per ogni IExperimentalAPIInvoker istanza dell'oggetto. Dopo la registrazione, non rilasciare l'oggetto fino a quando non si annulla la registrazione.
| Nome del parametro | TIPO | Description |
|---|---|---|
"targetWindow" |
VT_I8 |
HWND, convertito a un LONGLONG firmato, che riceve un messaggio di finestra quando l'app viene avviata per la sua piastrella principale nel menu Start o in altre posizioni. |
"copyDataFormatId" |
VT_UI4 |
Valore passato come COPYDATASTRUCT.dwData durante il recapito della stringa URI. |
"schemes" |
VT_LPWSTR \| VT_VECTOR |
Elenco di schemi URI per cui eseguire la registrazione. Solo quelli per cui il processo chiamante è il gestore predefinito vengono accelerati. |
Metodo: "UnregisterAcceleratedUriLaunch"
Annullare la registrazione dagli avvii URI. Nessun argomento. Deve essere chiamato sullo stesso oggetto registrato in precedenza.
Metodo: "RegisterAcceleratedTileLaunch"
Registrarsi per i lanci delle tile notificati tramite un messaggio di finestra scelto. È possibile chiamare questo metodo una sola volta per ogni IExperimentalAPIInvoker istanza dell'oggetto. Dopo la registrazione, non rilasciare l'oggetto finché non si annulla la registrazione.
| Nome del parametro | TIPO | Description |
|---|---|---|
"targetWindow" |
VT_I8 |
HWND, convertito a un LONGLONG firmato, che riceve un messaggio di finestra quando l'app viene avviata per la sua piastrella principale nel menu Start o in altre posizioni. |
"messageId" |
VT_UI4 |
ID del messaggio della finestra che viene inviato alla finestra di destinazione specificata. |
Metodo: "UnregisterAcceleratedTileLaunch"
Cancellare la registrazione dagli avvii dei riquadri. Questo metodo non accetta argomenti. È necessario chiamarlo sullo stesso oggetto registrato in precedenza.
Combinazione di tutti gli elementi
Per semplificare i passaggi precedenti, in particolare per le app che non hanno facile accesso a una procedura di finestra esistente, questo esempio fornisce un helper autonomo che gestisce una finestra di lavoro e la registra per gli avvii accelerati. Devi creare questo oggetto su un thread con un message pump attivo (ad esempio il thread dell'interfaccia utente di un'app WinUI).
Innanzitutto, copia il codice seguente in un file di intestazione nel tuo progetto.
#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;
};
È ora possibile integrare questo codice nell'app:
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 questo codice, l'app trae vantaggio dai lanci accelerati nelle versioni del sistema operativo che supportano questa funzionalità.
Contenuti correlati
Istanziazione dell'app con l'API del ciclo di vita dell'app
API del ciclo di vita delle app Windows App SDK