Migrar a C++/WinRT desde WRL

En este tema se muestra cómo convertir el código de la biblioteca de plantillas de C++ de Windows Runtime (WRL) a su equivalente en C++/WinRT.

El primer paso para migrar a C++/WinRT es agregar manualmente compatibilidad con C++/WinRT al proyecto (consulte Visual Studio compatibilidad con C++/WinRT). Para ello, instale el paquete NuGet Microsoft.Windows.CppWinRT en su proyecto. Abra el project en Visual Studio, haga clic en Project>Administrar paquetes NuGet...>Examine, escriba o pegue Microsoft.Windows. CppWinRT en el cuadro de búsqueda, seleccione el elemento en los resultados de búsqueda y, a continuación, haga clic en Instalar para instalar el paquete para ese project. Un efecto de ese cambio es que la compatibilidad con C++/CX está desactivada en el proyecto. Si usas C++/CX en el proyecto, puedes dejar la compatibilidad desactivada y migrar también tu código de C++/CX a C++/WinRT (consulta Pasa de C++/CX a C++/WinRT). O bien, puede volver a activar la compatibilidad (en las propiedades del proyecto, C/C++>General>Consume Windows Runtime Extensión>Sí (/ZW)) y centrarse primero en migrar el código WRL. El código de C++/CX y C++/WinRT puede coexistir en el mismo proyecto, a excepción de la compatibilidad con el compilador XAML y los componentes de Windows Runtime (consulta Mover a C++/WinRT desde C++/CX).

Establezca la propiedad de proyecto General>Target Platform Version en 10.0.17134.0 (Windows 10, versión 1803) o posterior.

En el archivo de encabezado precompilado (normalmente pch.h), incluya winrt/base.h.

#include <winrt/base.h>

Si incluye alguno de los encabezados de la API de Windows proyectados por C++/WinRT (por ejemplo, winrt/Windows.Foundation.h), no necesita incluir explícitamente winrt/base.h de esta manera, porque se incluirá automáticamente.

Portar punteros inteligentes COM WRL (Microsoft::WRL::ComPtr)

Portar cualquier código que use Microsoft::WRL::ComPtr<T> para usar winrt::com_ptr<T>. Este es un ejemplo de código anterior y posterior. En la versión después, la función miembro com_ptr::put recupera el puntero sin formato subyacente para poder asignarlo.

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

Si tienes un winrt::com_ptr que ya está sentado (su puntero sin procesar interno ya tiene un destino) y quieres volver a sentarlo para que apunte a un objeto diferente, primero debes asignarlo nullptr , como se muestra en el ejemplo de código siguiente. Si no es así, un com_ptr ya inicializado le señalará el problema (cuando llame a com_ptr::put o com_ptr::put_void) mediante una aserción de que su puntero interno no es nulo.

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

En el siguiente ejemplo (en la versión posterior), la función miembro com_ptr::put_void recupera el puntero sin formato subyacente como un puntero a puntero a 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();
}

Reemplace ComPtr::Get por com_ptr::get.

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

Cuando quiera pasar el puntero sin procesar subyacente a una función que espera un puntero a IUnknown, use la función libre winrt::get_unknown , como se muestra en este ejemplo siguiente.

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

Adaptación de un módulo WRL (Microsoft::WRL::Module)

Esta sección está relacionada con el código de portabilidad que usa el tipo Microsoft::WRL::Module.

Puede agregar gradualmente código de C++/WinRT a un proyecto existente que use WRL para implementar un componente y las clases WRL existentes seguirán siendo compatibles. En esta sección se muestra cómo.

Si crea un nuevo tipo de proyecto componente de Windows Runtime (C++/WinRT) en Visual Studio y compila, el archivo Generated Files\module.g.cpp se genera automáticamente. Ese archivo contiene las definiciones de dos funciones útiles de C++/WinRT (enumeradas a continuación), que puede copiar y agregar al proyecto. Esas funciones son WINRT_CanUnloadNow y WINRT_GetActivationFactory y, como puede ver, recurren de forma condicional a WRL para darle soporte, sea cual sea la fase del proceso de adaptación en la que se encuentre.

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

Una vez que tenga estas funciones en el proyecto, en lugar de llamar directamente a Module::GetActivationFactory , llame a WINRT_GetActivationFactory (que llama a la función WRL internamente). Este es un ejemplo de código anterior y posterior.

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

En lugar de llamar directamente a Module::Terminate , llame a WINRT_CanUnloadNow (que llama a la función WRL internamente). Este es un ejemplo de código anterior y posterior.

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;
}

Migración de los wrappers de Microsoft::WRL::Wrappers

Esta sección está relacionada con el código de portabilidad que usa los contenedores Microsoft::WRL::Wrappers.

Como puede ver en la tabla siguiente, para reemplazar los asistentes de subprocesos, se recomienda usar la biblioteca de compatibilidad con subprocesos estándar de C++. Una correspondencia uno a uno de los envoltorios de WRL podría ser engañosa, ya que la elección depende de sus necesidades. Además, algunos tipos que podrían parecer correspondencias evidentes son nuevos en el estándar C++20, por lo que esas correspondencias no resultarán prácticas si aún no has actualizado.

Type Notas de portabilidad
clase CriticalSection Utilice la biblioteca de compatibilidad con hilos
Clase de eventos (WRL) Usar la plantilla de estructura winrt::event
Clase HandleT Use la estructura winrt::handle o la estructura winrt::file_handle
clase HString Use la estructura winrt::hstring
clase HStringReference Ningún reemplazo, ya que C++/WinRT controla esto internamente de una manera tan eficaz como HStringReference con la ventaja de que no tiene que pensar en ello.
Clase Mutex Use la biblioteca de compatibilidad con hilos
clase RoInitializeWrapper Utilice winrt::init_apartment y winrt::uninit_apartment; o escriba su propio envoltorio sencillo para CoInitializeEx y CoUninitialize.
Clase Semaphore Usar la biblioteca de soporte para subprocesos
Clase SRWLock Utilice la biblioteca de compatibilidad con hilos

API importantes