Переход на 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 | Используйте библиотеку поддержки потоков |