从 WRL 移动到 C++/WinRT

本主题介绍如何将 Windows 运行时 C++ 模板库 (WRL) 代码移植到其在 C++/WinRT 中的等效实现。

移植到 C++/WinRT 的第一步是将 C++/WinRT 支持手动添加到项目(请参阅Visual Studio对 C++/WinRT 的支持)。 为此,请在项目中安装 Microsoft.Windows.CppWinRT NuGet package。 在 Visual Studio 中打开该项目,单击 Project>管理 NuGet 程序包...>浏览,在搜索框中键入或粘贴 Microsoft.Windows.CppWinRT,在搜索结果中选择该项,然后单击 安装,为该项目安装该程序包。 该更改的一个效果是在项目中关闭对 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)

将任何使用 Microsoft::WRL::ComPtr<T> 的代码迁移为使用 winrt::com_ptr<T>。 下面是之前和之后的代码示例。 在 after 版本中,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()));

Important

如果你有已固定的 winrt::com_ptr (其内部原始指针已有目标),并且想要重新定位它以指向其他对象,则首先需要分配给 nullptr 它,如下面的代码示例所示。 如果没有,那么一个已经持有对象的 com_ptr 会通过断言其内部指针不为 null 来提醒你这个问题(当你调用 com_ptr::putcom_ptr::put_void 时)。

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

在下一个示例中(在 after 版本中),com_ptr::put_void 成员函数以指向 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::Get 替换为 com_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
    )
);
// Note: In a WinUI 3 desktop app, use CreateSwapChainForHwnd instead of
// CreateSwapChainForCoreWindow, since WinUI 3 doesn't use CoreWindow.
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 类型的移植代码相关。

你可以逐步将 C++/WinRT 代码添加到使用 WRL 实现组件的现有项目中,并且将继续支持现有的 WRL 类。 本部分介绍如何操作。

如果在 Visual Studio 中创建新的Windows 运行时组件(C++/WinRT)项目类型,然后生成,则会为你生成该文件Generated Files\module.g.cpp。 该文件包含两个有用的 C++/WinRT 函数的定义(下面列出),你可以复制并添加到项目。 这些函数是 WINRT_CanUnloadNowWINRT_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 封装器作一对一对应可能会产生误导,因为具体如何选择取决于你的需求。 此外,某些看似明显的映射类型是 C++20 标准的新增功能,因此,如果尚未升级,这些类型将不切实际。

类型 移植笔记
CriticalSection 类 使用 线程支持库
事件类 (WRL) 使用 winrt::event 结构模板
HandleT 类 使用 winrt::handle 结构winrt::file_handle 结构
HString 类 使用 winrt::hstring 结构体
HStringReference 类 无需替代方案,因为 C++/WinRT 会在内部处理这件事,其效率与 HStringReference 一样高,而且好处是你完全不必操心。
Mutex 类 使用 线程支持库
RoInitializeWrapper 类 使用 winrt::init_apartmentwinrt::uninit_apartment;或围绕 CoInitializeExCoUninitialize 编写你自己的简单包装器。
信号量类 使用 线程支持库
SRWLock 类 使用 线程支持库

重要 API