Migrar a C++/WinRT desde WRL

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

El primer paso en la migración a C++/WinRT es agregar manualmente soporte C++/WinRT a tu proyecto (consulta Soporte de Visual Studio para C++/WinRT). Para ello, instala el paquete NuGet Microsoft.Windows.CppWinRT en el proyecto. Abra el proyecto en Visual Studio, haga clic en Proyecto>Administrar paquetes de NuGet...>Examinar, escriba o pegue Microsoft.Windows.CppWinRT en el cuadro de búsqueda, seleccione el elemento en los resultados de la búsqueda y luego haga clic en Instalar para instalar el paquete para ese proyecto. Un efecto de ese cambio es que el soporte para C++/CX está desactivado en el proyecto. Si usas C++/CX en el proyecto, puedes dejar el soporte desactivado y actualizar tu código de C++/CX a C++/WinRT también (consulta Mover a C++/ WinRT desde C++/CX). O bien puedes volver a activar el soporte (en las propiedades del proyecto, C/C++>General>Usar extensión de Windows Runtime>Sí (/ZW)) y centrarte primero en portar tu 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 Migrar a C++/WinRT desde C++/CX).

Establece la propiedad de proyecto General>Versión de la plataforma de destino en 10.0.17134.0 (Windows 10, versión 1803) o superior.

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

#include <winrt/base.h>

Si incluyes cualquier encabezado de API de Windows proyectado de C++/ WinRT (por ejemplo, winrt/Windows.Foundation.h), no necesitas incluir explícitamente winrt/base.h de este modo, porque se incluirá automáticamente para ti.

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

Migre cualquier código que use Microsoft::WRL::ComPtr<T> para que use winrt::com_ptr<T>. Aquí se muestra un ejemplo de código de "antes" y "después". En la versión de después, la función miembro com_ptr::put recupera el puntero sin procesar subyacente para que se pueda establecer.

ComPtr<IDXGIAdapter1> previousDefaultAdapter;
DX::ThrowIfFailed(m_dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter));
winrt::com_ptr<IDXGIAdapter1> previousDefaultAdapter;
winrt::check_hresult(m_dxgiFactory->EnumAdapters1(0, previousDefaultAdapter.put()));

Importante

Si tiene una función winrt::com_ptr que ya se ha asentado (es decir, el puntero interno sin procesar ya tiene un destino) y quiere volver a asentarla para que señale a un objeto distinto, en primer lugar deberá asignarle nullptr, tal como se muestra en el ejemplo de código siguiente. Si no lo haces, una función com_ptr ya asentada atraerá tu atención hacia el problema (cuando llames a com_ptr::put o com_ptr:: put_void) mediante la declaración de que el puntero interno no es 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())
);

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

Reemplaza 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 quieras pasar el puntero sin procesar subyacente a una función que espera un puntero a IUnknown, usa la función gratuita winrt::get_unknown, tal como se muestra en el siguiente ejemplo.

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

Portabilidad a un módulo WRL (Microsoft::WRL::Module)

Esta sección se relaciona con el código de portabilidad que usa el tipo Microsoft::WRL::Module.

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

Si creas un nuevo proyecto de Componente de Windows Runtime (C++/WinRT) en Visual Studio y lo compilas, se generará el archivo Generated Files\module.g.cpp. Este archivo contiene las definiciones de dos funciones útiles de C++/WinRT (que se enumeran debajo) que puedes copiar y agregar al proyecto. Esas funciones son WINRT_CanUnloadNow y WINRT_GetActivationFactory y, como puedes ver, llaman de forma condicional a WRL para darte soporte en cualquier fase de la portabilidad en la que te encuentres.

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 tengas estas funciones en tu proyecto, en lugar de llamar a Module::GetActivationFactory directamente, llama a WINRT_GetActivationFactory (que llama a la función WRL internamente). Aquí se muestra un ejemplo de código de "antes" y "después".

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 a Module::Terminate directamente, llama a WINRT_CanUnloadNow (que llama a la función WRL internamente). Aquí se muestra un ejemplo de código de "antes" y "después".

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

Portabilidad de contenedores Microsoft::WRL::Wrappers

Esta sección se relaciona con el código de portabilidad que usa los contenedores Microsoft::WRL::Wrappers.

Tal como puede ver en la tabla siguiente, para reemplazar los asistentes de subprocesos, se recomienda usar la biblioteca de compatibilidad con subprocesos de C++ estándar. Podría resultar confuso asignar uno a uno los contenedores de WRL, ya que su elección depende de sus necesidades. Asimismo, algunos tipos que pueden parecer asignaciones obvias son nuevos en el estándar C++20, por lo que no serán prácticos si aún no los ha actualizado.

Tipo Notas de portabilidad
clase CriticalSection Use la biblioteca de compatibilidad con subprocesos.
Event (Clase) (WRL) Use la plantilla de estructura winrt::event.
HandleT (Clase) Use la estructura winrt::handle o la winrt::file_handle.
HString (Clase) Use la estructura winrt::hstring.
HStringReference (Clase) Sin reemplazo, ya que C++/WinRT la controla internamente de una manera tan eficaz como HStringReference, con la ventaja de que no tiene que preocuparse por ella.
Mutex (clase) Use la biblioteca de compatibilidad con subprocesos.
RoInitializeWrapper (Clase) Use winrt::init_apartment y winrt::uninit_apartment, o escriba su propio contenedor trivial alrededor de CoInitializeEx y CoUninitialize.
Semaphore (Clase) Use la biblioteca de compatibilidad con subprocesos.
SRWLock (Clase) Use la biblioteca de compatibilidad con subprocesos.

API importantes