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

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

Первым шагом при переносе в C++/WinRT является ручное добавление поддержки C++/WinRT в проект (см. раздел Поддержка для Visual Studio C++/WinRT, XAML, расширения VSIX и пакет NuGet). Для этого в свой проект следует установить пакет Microsoft.Windows.CppWinRT NuGet. В Visual Studio откройте проект, выберите Проект>Управление пакетами NuGet...>Обзор, введите или вставьте Microsoft.Windows.CppWinRT в поле поиска, выберите элемент в результатах поиска, а затем нажмите кнопку Установить чтобы установить пакет для этого проекта. Одним из последствий этого изменения является отключение поддержки для C++/CX в проекте. Если вы используете C++/CX в проекте, впоследствии можно оставить поддержку отключенной и обновить код C++/CX до кода C++/WinRT (см. раздел Переход на C++/WinRT с C++/CX). Либо можно включить поддержку (в свойствах проекта перейдите в раздел C/C++>Общие>Использовать расширение среды выполнения Windows>Да (/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), утверждая, что его внутренний указатель не равен нулю.

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 извлекает базовый необработанный указатель в качестве указателя на указатель типа "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
    )
);
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 останется активна. В этом разделе показано, как это сделать.

При создании нового проекта типа Компонент среды выполнения Windows (C++/WinRT) в Visual Studio и в ходе последующей сборки создается файл 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::Wrappers

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

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

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

Важные API