アプリケーションを高速ウォーム起動用に登録する

この記事では、単一インスタンス Windows アプリケーションのウォーム 起動を高速化する方法について説明します。 起動が発生したときに直接通知されるように実行中のプロセス インスタンスを登録し、完全な起動フローに関連するオーバーヘッドを回避する方法について説明します。 この方法では、既に実行されているインスタンスと統合される中間プロセスを排除することで、全体的な待機時間が短縮されます。

現在の一般的なフロー:

中間プロセスを含む通常のウォーム起動経路を示すフロー図のスクリーンショット。

高速起動の場合:

中間プロセスなしの高速ウォーム起動経路を示すフロー図のスクリーンショット。

この高速起動機能は、すべてのバージョンの Windows 上のすべてのソース アプリケーションからのすべての種類の起動をカバーするわけではありません。 その結果、アプリケーションは単一インスタンス化の独自の実装を維持する必要があります (1 つのアプローチについては、 アプリ ライフサイクル API を使用したアプリのインスタンス化を 参照してください)。

この記事の残りの部分では、高速ウォーム スタート メニューとプロトコル起動をサポートする単純な C++ WinUI 3 アプリを作成する手順について説明します。

Important

高速起動 API は、制限付きAccess機能の一部です (LimitedAccessFeatures クラスを参照)。 詳細またはロック解除トークンを要求するには、LAF Access トークン要求フォームを使用します。

新しいprojectを作成する

Visual Studioで、新しいprojectを作成します。 この例では、新しいproject ダイアログで、言語フィルターを C++ に設定し、projectの種類を WinUI に設定し、 WinUI 空のアプリ (パッケージ化) を選択します。 このテンプレートが表示されない場合は、Visual Studio インストーラーに WinUI アプリケーション開発 ワークロードがインストールされていることを確認します。

単一インスタンス化を実装する

任意の手法を使用して、アプリ内に単一インスタンス化を実装できます。 この例では、Windows App SDK のアプリ ライフサイクル API を使用します。

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

アプリのプロトコルの関連付けを追加する

高速プロトコルの起動を受信するには、アプリがプロトコル登録の既定のターゲットである必要があります。 このサンプルでは、パッケージ化された登録を使用します。 URI スキームの既定のハンドラーとして登録する方法の詳細については、「 URI アクティブ化の処理」を参照してください。

Package.appxmanifestで、プロトコル関連付けのエントリを追加します。

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

これで、スタート メニューのタイルを使用するか、[実行] ダイアログに「 acceleratedlaunchsample: 」と入力して、アプリを起動できるようになりました。 どちらの場合も、中間プロセスが起動し、既に実行されているインスタンスにリダイレクトされます。

ローンチの加速のために登録する

これで、アプリで最適化をオプトインする準備ができました。

高速起動メッセージは、プロセス内の HWND にウィンドウ メッセージとして配信されます。 IExperimentalAPIInvokerによって実装されるインターフェイスCLSID_ExperimentalAPIInvokerを使用して、メッセージを受信するウィンドウと使用するメッセージをシステムに指示します。

IExperimentalAPIInvokerCLSID_ExperimentalAPIInvoker は次のように定義されます。

[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 では、名前付きプロパティ ストアにパッケージ化された引数を使用して、名前でメソッドを呼び出すことができます。 高速起動の方法は次のとおりです。

メソッド: "RegisterAcceleratedUriLaunch"

WM_COPYDATA を介してターゲット HWND に null 終端の UTF-16 文字列 (PCWSTR) として配信されるプロトコル起動に登録します。

このメソッドは、 IExperimentalAPIInvoker オブジェクト インスタンスごとに 1 回だけ呼び出すことができます。 登録が完了したら、登録を解除するまでオブジェクトを解放しないでください。

パラメーター名 タイプ Description
"targetWindow" VT_I8 アプリが[スタート]メニューやその他の場所でプライマリタイルとして起動されたとき、HWNDが符号付きLONGLONGにキャストされ、ウィンドウメッセージを受信します。
"copyDataFormatId" VT_UI4 URI 文字列を配信するときに COPYDATASTRUCT.dwData として渡される値。
"schemes" VT_LPWSTR \| VT_VECTOR 登録する URI スキームの一覧。 呼び出し元プロセスが既定のハンドラーであるプロセスのみが高速化されます。

メソッド: "UnregisterAcceleratedUriLaunch"

URI ランチャーから登録解除します。 引数はありません。 以前に登録したのと同じオブジェクトで呼び出す必要があります。

メソッド:「RegisterAcceleratedTileLaunch」

選択したウィンドウメッセージで通知されるタイルの起動を受け取るために登録します。 このメソッドは、 IExperimentalAPIInvoker オブジェクト インスタンスごとに 1 回だけ呼び出すことができます。 登録後は、登録を解除するまでオブジェクトを解放しないでください。

パラメーター名 タイプ Description
"targetWindow" VT_I8 アプリが[スタート]メニューやその他の場所でプライマリタイルとして起動されたとき、HWNDが符号付きLONGLONGにキャストされ、ウィンドウメッセージを受信します。
"messageId" VT_UI4 指定したターゲット ウィンドウに移動するウィンドウ メッセージ ID。

メソッド: "UnregisterAcceleratedTileLaunch"

タイルの起動から登録を解除します。 このメソッドは引数を受け取っていません。 以前に登録したのと同じオブジェクトで呼び出す必要があります。

まとめる

前の手順 (特に、既存のウィンドウ プロシージャに簡単にaccessできないアプリの場合) を簡略化するために、このサンプルでは、ワーカー ウィンドウを管理し、高速起動に登録する自己完結型ヘルパーを提供します。 このオブジェクトは、アクティブなメッセージ ポンプ (WinUI アプリの UI スレッドなど) を使用してスレッド上に作成する必要があります。

まず、次のコードを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;
};

これで、このコードをアプリに統合できます。

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

このコードを使用すると、アプリは、この機能をサポートする OS バージョンで高速起動を利用できます。

URI のアクティブ化を処理する

アプリ ライフサイクル API を使用したアプリのインスタンス化

Windows App SDK のアプリ ライフサイクル API