WRL から C++/WinRT への移行

このトピックでは、Windows ランタイム C++ テンプレート ライブラリ (WRL) のコードを C++/WinRT の同等のコードに移植する方法について説明します。

C++/WinRT への移植の最初の手順は、C++/WinRT サポートをプロジェクトに手動で追加することです (Visual Studio support for C++/WinRT (Visual Studio での C++/WinRT のサポート) に関する記事を参照)。 そのためには、Microsoft.Windows.CppWinRT NuGet パッケージをご利用のプロジェクトにインストールします。 Visual Studio でプロジェクトを開き、[プロジェクト]>[NuGet パッケージの管理...]>[参照] とクリックし、検索ボックスに「Microsoft.Windows.CppWinRT」と入力するか貼り付けます。検索結果の項目を選択し、[インストール] をクリックしてそのプロジェクトにパッケージをインストールします。 その変更による 1 つの効果は、C++/CX のサポートがプロジェクトで無効になることです。 プロジェクトで C++/CX を使用している場合は、サポートを無効にしたままにし、C++/CX コードを C++/WinRT に更新することもできます (「C++/CX から C++/WinRT への移行」を参照してください)。 または、サポートをもう一度有効にし (プロジェクトのプロパティで、[C/C++]>[全般]>[Windows ランタイム拡張機能の使用]>[はい (/ZW)] の順に選択)、まず WRL コードを移植することに集中することもできます。 C++/CX コードと C++/WinRT コードは同じプロジェクト内に共存できます。ただし、XAML コンパイラのサポートと Windows ランタイム コンポーネントについては例外です (「C++/CX から C++/WinRT への移行」を参照)。

プロジェクトのプロパティ ([全般]>[ターゲット プラットフォーム バージョン]) を 10.0.17134.0 (Windows 10 バージョン 1803) 以上に設定します。

プリコンパイル済みヘッダー ファイル (通常は pch.h) で、winrt/base.h を含めます。

#include <winrt/base.h>

C++/WinRT の投影された Windows API ヘッダー (たとえば、winrt/Windows.Foundation.h) を含める場合は、それが自動的に含められるため、このように明示的に winrt/base.h を含める必要はありません。

WRL COM スマート ポインター (Microsoft::WRL::ComPtr) の移植

winrt::com_ptr<T> を使うには、Microsoft::WRL::ComPtr<T> を使用するコードを移植します。 変更前と変更後のコード例を次に示します。 変更後のバージョンでは、com_ptr::put メンバー関数は基になる生のポインターを取得して設定できるようにします。

ComPtr<IDXGIAdapter1> previousDefaultAdapter;
DX::ThrowIfFailed(m_dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter));
winrt::com_ptr<IDXGIAdapter1> previousDefaultAdapter;
winrt::check_hresult(m_dxgiFactory->EnumAdapters1(0, previousDefaultAdapter.put()));

重要

winrt::com_ptr が既に設定されていて (内部の生のポインターが既にターゲットを持っていて)、別のオブジェクトを指すように再設定する場合は、次のコード例に示すように、最初に nullptr をそれに割り当てる必要があります。 そうしない場合、(com_ptr::put または com_ptr::put_void を呼び出すときに) 既に設定されている com_ptr によって内部ポインターが null ではないとアサートされるため、問題に気づきます。

winrt::com_ptr<IDXGISwapChain1> m_pDXGISwapChain1;
...
// We execute the code below each time the window size changes.
m_pDXGISwapChain1 = nullptr; // Important because we're about to re-seat 
winrt::check_hresult(
    m_pDxgiFactory->CreateSwapChainForHwnd(
        m_pCommandQueue.get(), // For Direct3D 12, this is a pointer to a direct command queue, and not to the device.
        m_hWnd,
        &swapChainDesc,
        nullptr,
        nullptr,
        m_pDXGISwapChain1.put())
);

次の例 (変更後のバージョン) では、com_ptr::put_void メンバー関数は基になる生のポインターを、無効にするポインターへのポインターとして取得します。

ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
    debugController->EnableDebugLayer();
}
winrt::com_ptr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(__uuidof(debugController), debugController.put_void())))
{
    debugController->EnableDebugLayer();
}

ComPtr::Getcom_ptr::get で置換します。

m_d3dDevice->CreateDepthStencilView(m_depthStencil.Get(), &dsvDesc, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
m_d3dDevice->CreateDepthStencilView(m_depthStencil.get(), &dsvDesc, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());

基になる生のポインターを IUnknown へのポインターを受け取る関数に渡す場合は、次の例に示すように、winrt::get_unknown 自由関数を使用します。

ComPtr<IDXGISwapChain1> swapChain;
DX::ThrowIfFailed(
    m_dxgiFactory->CreateSwapChainForCoreWindow(
        m_commandQueue.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &swapChain
    )
);
winrt::agile_ref<winrt::Windows::UI::Core::CoreWindow> m_window; 
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::check_hresult(
    m_dxgiFactory->CreateSwapChainForCoreWindow(
        m_commandQueue.get(),
        winrt::get_unknown(m_window.get()),
        &swapChainDesc,
        nullptr,
        swapChain.put()
    )
);

WRL モジュール (Microsoft::WRL::Module) の移植

このセクションは、Microsoft::WRL::Module 型を使用するコードの移植に関連しています。

WRL を使用してコンポーネントを実装する既存のプロジェクトに C++/WinRT コードを徐々に追加することができます。既存の WRL クラスは引き続きサポートされます。 このセクションではその方法を示します。

Visual Studio でプロジェクトの種類として新しい Windows ランタイム コンポーネント (C++/WinRT) を作成する場合は、ファイル Generated Files\module.g.cpp が自動的に生成されます。 そのファイルには、以下に示す 2 つ便利な C++/WinRT 関数の定義があり、これをプロジェクトにコピーして追加することができます。 これらの関数は WINRT_CanUnloadNow および WINRT_GetActivationFactory です。ご覧になってわかるように、これらの関数は移植のどの段階でも開発者にサポートを提供できるように、条件付きで WRL を呼び出します。

HRESULT WINRT_CALL WINRT_CanUnloadNow()
{
#ifdef _WRL_MODULE_H_
    if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
    {
        return S_FALSE;
    }
#endif

    if (winrt::get_module_lock())
    {
        return S_FALSE;
    }

    winrt::clear_factory_cache();
    return S_OK;
}

HRESULT WINRT_CALL WINRT_GetActivationFactory(HSTRING classId, void** factory)
{
    try
    {
        *factory = nullptr;
        wchar_t const* const name = WINRT_WindowsGetStringRawBuffer(classId, nullptr);

        if (0 == wcscmp(name, L"MoveFromWRLTest.Class"))
        {
            *factory = winrt::detach_abi(winrt::make<winrt::MoveFromWRLTest::factory_implementation::Class>());
            return S_OK;
        }

#ifdef _WRL_MODULE_H_
        return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().GetActivationFactory(classId, reinterpret_cast<::IActivationFactory**>(factory));
#else
        return winrt::hresult_class_not_available().to_abi();
#endif
    }
    catch (...) { return winrt::to_hresult(); }
}

プロジェクトにこれらの関数を含めたら、Module::GetActivationFactory を直接呼び出すのではなく、WINRT_GetActivationFactory を呼び出します (これにより WRL 関数が内部的に呼び出されます)。 変更前と変更後のコード例を次に示します。

HRESULT WINAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _Out_ ::IActivationFactory **factory)
{
    auto & module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule();
    return module.GetActivationFactory(activatableClassId, factory);
}
HRESULT __stdcall WINRT_GetActivationFactory(HSTRING activatableClassId, void** factory);
HRESULT WINAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _Out_ ::IActivationFactory **factory)
{
    return WINRT_GetActivationFactory(activatableClassId, reinterpret_cast<void**>(factory));
}

Module::Terminate を直接呼び出す代わりに、WINRT_CanUnloadNow を呼び出します (これにより WRL 関数が内部的に呼び出されます)。 変更前と変更後のコード例を次に示します。

HRESULT __stdcall DllCanUnloadNow(void)
{
    auto &module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule();
    HRESULT hr = (module.Terminate() ? S_OK : S_FALSE);
    if (hr == S_OK)
    {
        hr = ...
    }
    return hr;
}
HRESULT __stdcall WINRT_CanUnloadNow();
HRESULT __stdcall DllCanUnloadNow(void)
{
    HRESULT hr = WINRT_CanUnloadNow();
    if (hr == S_OK)
    {
        hr = ...
    }
    return hr;
}

Microsoft::WRL::Wrappers ラッパーの移植

このセクションは、Microsoft::WRL::Wrappers ラッパーを使用するコードの移植に関連しています。

下の表でわかるように、スレッド処理のヘルパーを置き換えるには、標準 C++ スレッド サポート ライブラリを使用することをお勧めします。 WRL ラッパーの 1 対 1 のマッピングは、選択はニーズに依存するため、間違った方向に導く可能性があります。 また、明らかなマッピングのように見える一部の型は、C++20 標準では新しいため、まだアップグレードしていない場合は実用的ではありません。

Type 移植に関する注意事項
CriticalSection クラス スレッド サポート ライブラリを使用
イベント クラス (WRL) winrt::event 構造体テンプレートを使用
HandleT クラス winrt::handle 構造体または winrt::file_handle 構造体を使用
HString クラス winrt::hstring struct を使用
HStringReference クラス 代わりになるものなし。C++/WinRT では、HStringReference と同じ効率的な方法で内部的にこれが処理されるため、これについて考える必要がないという利点があります。
Mutex クラス スレッド サポート ライブラリを使用
RoInitializeWrapper クラス winrt::init_apartmentwinrt::uninit_apartment を使用するか、CoInitializeEx および CoUninitialize を囲む簡単な独自のラッパー記述します。
Semaphore クラス スレッド サポート ライブラリを使用
SRWLock クラス スレッド サポート ライブラリを使用

重要な API