Переход на C++/WinRT с WRL

В этом разделе показано, как перенести код среда выполнения Windows библиотеки шаблонов C++ (WRL) в эквивалент C++/WinRT.

Первым шагом при переносе на C++/WinRT необходимо вручную добавить поддержку C++/WinRT в свой проект (см. Поддержка C++/WinRT в Visual Studio). Для этого установите в свой проект пакет NuGet Microsoft.Windows.CppWinRT. Откройте проект в Visual Studio, щелкните Project>Manage NuGet Packages...>Browse, введите или вставьте Microsoft.Windows.CppWinRT в поле поиска, выберите элемент в результатах поиска, а затем нажмите Install, чтобы установить пакет для этого проекта. Одним из последствий этого изменения является отключение поддержки C++/CX в проекте. Если вы используете C++/CX в проекте, вы можете оставить поддержку отключенной и обновить код C++/CX на C++/WinRT, а также (см. статью "Переход на C++/WinRT" из C++/CX). Вы также можете включить поддержку (в свойствах проекта, C/C++>General>Consume среда выполнения Windows Extension>Yes (/ZW)) и сначала сосредоточиться на переносе кода WRL. Код C++/CX и C++/WinRT может сосуществовать в одном проекте, за исключением поддержки компилятора XAML, и среда выполнения Windows компонентов (см. статью "Переход на C++/WinRT" из C++/CX).

Установите для свойства проекта Общие>Версия целевой платформы значение 10.0.17134.0 (Windows 10, версия 1803) или выше.

В предварительно скомпилированном файле заголовка (обычно pch.h) подключите winrt/base.h.

#include <winrt/base.h>

Если вы включаете какие-либо проецируемые заголовки Windows API для C++/WinRT (например, winrt/Windows.Foundation.h), то вам не нужно явно включать winrt/base.h, поскольку он будет включён автоматически.

Перенос смарт-указателей WRL COM (Microsoft::WRL::ComPtr)

Перенесите весь код, использующий Microsoft::WRL::ComPtr<T>, на использование winrt::com_ptr<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 укажет на проблему (при вызове com_ptr::put или com_ptr::put_void), сгенерировав утверждение о том, что его внутренний указатель не равен 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::p ut_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_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::Оболочки

Этот раздел посвящён переносу кода, использующего классы-обёртки Microsoft::WRL::Wrappers.

Как видно из приведённой ниже таблицы, для замены вспомогательных средств для работы с потоками мы рекомендуем использовать стандартную библиотеку C++ для поддержки потоков. Сопоставление один к одному из оболочки WRL может быть вводящим в заблуждение, так как ваш выбор зависит от ваших потребностей. Кроме того, некоторые типы, которые могут показаться очевидными соответствиями, появились только в стандарте C++20, поэтому использовать их будет непрактично, если вы еще не перешли на него.

Type Примечания по портированию
Класс CriticalSection Используйте библиотеку поддержки потоков
Класс событий (WRL) Использование шаблона структуры winrt::event
Класс HandleT Использование структуры winrt::handle или структуры winrt::file_handle
Класс HString Используйте структуру winrt::hstring
Класс HStringReference Нет замены, так как C++/WinRT обрабатывает это внутренне так же эффективно, как HStringReference с преимуществом, что вам не нужно думать об этом.
Класс Mutex Используйте библиотеку поддержки потоков
Класс RoInitializeWrapper Используйте winrt::init_apartment и winrt::uninit_apartment; или напишите собственную тривиальную оболочку вокруг CoInitializeEx и CoUninitialize.
Класс Семафор Используйте библиотеку поддержки потоков
Класс SRWLock Используйте библиотеку поддержки потоков

Важные API