Interoperabilidad entre C++/WinRT y C++/CX

Antes de leer este tema, necesitará la información que aparece en el tema Cambiar de C++/CX a C++/WinRT. En este tema se presentan dos opciones de estrategia principales para migrar el proyecto de C++/CX a C++/WinRT.

  • Migra todo el proyecto de una sola vez. La opción más sencilla para un proyecto que no es demasiado grande. Si tiene un proyecto de componente de Windows Runtime, esta estrategia es la única opción.
  • Portar el proyecto gradualmente (el tamaño o la complejidad del código base podría hacer esto necesario). Pero esta estrategia le pide que siga un proceso de portabilidad en el que, durante una vez, el código de C++/CX y C++/WinRT existe en paralelo en el mismo proyecto. Para un proyecto XAML, en un momento dado, los tipos de las páginas XAML deben ser o bien todos de C++/WinRT o bien todos de C++/CX.

Este tema de interoperabilidad es relevante para esa segunda estrategia: en los casos en los que necesita migrar el proyecto gradualmente. En este tema se muestran varias formas de pares de funciones auxiliares que se pueden usar para convertir un objeto de C++/CX (y otros tipos) en un objeto de C++/WinRT (y viceversa) dentro del mismo proyecto.

Estas funciones auxiliares serán muy útiles a medida que el código se porta gradualmente de C++/CX a C++/WinRT. O bien, podrías optar por usar las proyecciones del lenguaje C++/WinRT y C++/CX en el mismo proyecto, tanto si vas a migrar como si no, y usas estas funciones auxiliares para interoperar entre los dos.

Después de leer este tema, para obtener información y ejemplos de código que muestran cómo admitir tareas y corrutinas PPL en paralelo en el mismo proyecto (por ejemplo, llamar a corrutinas desde cadenas de tareas), vea el tema más avanzado Asynchrony e interoperabilidad entre C++/WinRT y C++/CX.

Las funciones from_cx y to_cx

Esta es una lista de código fuente de un archivo de encabezado denominado interop_helpers.h, que contiene varias funciones auxiliares de conversión. A medida que se traslada gradualmente el proyecto, habrá partes todavía en C++/CX y partes que haya migrado a C++/WinRT. Puede usar estas funciones auxiliares para convertir objetos (y otros tipos) en C++/CX y C++/WinRT en el proyecto en los puntos de límite entre esas dos partes.

En las secciones siguientes a la lista de código se explican las funciones auxiliares y cómo crear y usar el archivo de encabezado en el proyecto.

// interop_helpers.h
#pragma once

template <typename T>
T from_cx(Platform::Object^ from)
{
    T to{ nullptr };

    if (from != nullptr)
    {
        winrt::check_hresult(reinterpret_cast<::IUnknown*>(from)
            ->QueryInterface(winrt::guid_of<T>(), winrt::put_abi(to)));
    }

    return to;
}

template <typename T>
T^ to_cx(winrt::Windows::Foundation::IUnknown const& from)
{
    return safe_cast<T^>(reinterpret_cast<Platform::Object^>(winrt::get_abi(from)));
}

inline winrt::hstring from_cx(Platform::String^ const& from)
{
    return reinterpret_cast<winrt::hstring&>(const_cast<Platform::String^&>(from));
}

inline Platform::String^ to_cx(winrt::hstring const& from)
{
    return reinterpret_cast<Platform::String^&>(const_cast<winrt::hstring&>(from));
}

inline winrt::guid from_cx(Platform::Guid const& from)
{
    return reinterpret_cast<winrt::guid&>(const_cast<Platform::Guid&>(from));
}

inline Platform::Guid to_cx(winrt::guid const& from)
{
    return reinterpret_cast<Platform::Guid&>(const_cast<winrt::guid&>(from));
}

Función from_cx

La función auxiliar from_cx convierte un objeto C++/CX en un objeto C++/WinRT equivalente. La función convierte un objeto de C++/CX en su puntero de interfaz IUnknown subyacente. A continuación, llama a QueryInterface en ese puntero para consultar la interfaz predeterminada del objeto C++/WinRT. QueryInterface es el equivalente en la interfaz binaria de aplicación (ABI) de Windows Runtime de la extensión safe_cast de C++/CX. Además, la función winrt::put_abi obtiene la dirección del puntero subyacente a la interfaz IUnknown de un objeto de C++/WinRT para poder establecerlo en otro valor.

Función to_cx

La función auxiliar to_cx convierte un objeto C++/WinRT en un objeto C++/CX equivalente. La función winrt::get_abi recupera un puntero a la interfaz IUnknown subyacente de un objeto de C++/WinRT. La función convierte ese puntero a un objeto de C++/CX antes de usar la extensión C++/CX safe_cast para consultar el tipo de C++/CX solicitado.

El archivo de encabezado interop_helpers.h

Para usar las funciones auxiliares del proyecto, siga estos pasos.

  • Agregue un nuevo elemento Archivo de encabezado (.h) al proyecto y asígnele interop_helpers.hel nombre .
  • Sustituya el contenido de interop_helpers.h por el listado de código anterior.
  • Agregue estos elementos a pch.h.
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

Tomar un proyecto de C++/CX y agregar compatibilidad con C++/WinRT

En esta sección se describe qué hacer si ha decidido partir de un proyecto existente de C++/CX, agregar soporte para C++/WinRT y realizar allí el trabajo de migración. Consulte también Visual Studio compatibilidad con C++/WinRT.

Para combinar C++/CX y C++/WinRT en un proyecto de C++/CX —incluyendo el uso de las funciones auxiliares from_cx y to_cx en el proyecto—, tendrá que añadir manualmente la compatibilidad con C++/WinRT al proyecto.

En primer lugar, abra el proyecto de C++/CX en Visual Studio y confirme que la propiedad del proyecto General>Versión de la plataforma de destino está establecida en 10.0.17134.0 (Windows 10, versión 1803) o una versión posterior.

Instalación del paquete NuGet de C++/WinRT

El paquete NuGet Microsoft.Windows.CppWinRT proporciona soporte de compilación para C++/WinRT (propiedades y objetivos de MSBuild). Para instalarlo, haga clic en el elemento de menú 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.

Important

La instalación del paquete NuGet de C++/WinRT hace que la compatibilidad con C++/CX se desactive en el proyecto. Si va a hacer la migración de una sola vez, es buena idea dejar ese soporte desactivado para que los mensajes de compilación le ayuden a encontrar (y migrar) todas sus dependencias de C++/CX (con el tiempo, lo que era un proyecto exclusivamente de C++/CX terminará convirtiéndose en un proyecto exclusivamente de C++/WinRT). Pero consulta la sección siguiente para obtener información sobre cómo volver a activarla.

Volver a activar la compatibilidad con C++/CX

Si vas a migrar en un pase, no necesitas hacerlo. Pero si necesita migrar gradualmente, en este momento deberá volver a activar la compatibilidad con C++/CX en el proyecto. En las propiedades del proyecto, C/C++>General>Consumir la extensión de Windows Runtime>Sí (/ZW)).

Como alternativa (o, para un proyecto XAML, además), puedes agregar compatibilidad con C++/CX mediante la página de propiedades del proyecto de C++/WinRT en Visual Studio. En las propiedades del proyecto, Propiedades comunes>C++/WinRT>Idioma del proyecto>C++/CX. Si lo hace, agregará la siguiente propiedad al .vcxproj archivo.

  <PropertyGroup Label="Globals">
    <CppWinRTProjectLanguage>C++/CX</CppWinRTProjectLanguage>
  </PropertyGroup>

Important

Siempre que necesite realizar una compilación para procesar el contenido de un archivo Midl (.idl) en archivos stub, deberá cambiar Idioma del proyecto de nuevo a C++/WinRT. Una vez que la compilación haya generado esos stubs, vuelva a poner Project Language en C++/CX.

Para ver una lista de opciones de personalización similares (que ajustan con precisión el comportamiento de la herramienta cppwinrt.exe), consulte el archivo readme del paquete NuGet Microsoft.Windows.CppWinRT.

Incluir archivos de cabecera de C++/WinRT

Lo menos que debe hacer es que, en el archivo de encabezado precompilado (normalmente pch.h), incluya winrt/base.h como se muestra a continuación.

// pch.h
...
#include <winrt/base.h>
...

Pero casi con toda seguridad necesitarás los tipos del espacio de nombres winrt::Windows::Foundation. Y puede que ya conozca otros espacios de nombres que necesite. Por lo tanto, incluya los archivos de encabezado de la API de Windows de C++/WinRT correspondientes a esos espacios de nombres de esta manera (ahora no necesita incluir winrt/base.h explícitamente, porque se incluirá automáticamente).

// pch.h
...
#include <winrt/Windows.Foundation.h>
// Include any other C++/WinRT projected Windows API headers here.
...

Vea también el ejemplo de código en la sección siguiente (Tomar un proyecto de C++/WinRT y agregar compatibilidad con C++/CX) para obtener una técnica mediante los alias de espacio de nombres namespace cx y namespace winrt. Esa técnica permite gestionar posibles conflictos de espacios de nombres que, de otro modo, podrían producirse entre la proyección de C++/WinRT y la proyección de C++/CX.

Adición de interop_helpers.h al proyecto

Ahora podrá agregar las funciones from_cx y to_cx al proyecto de C++/CX. Para obtener instrucciones sobre cómo hacerlo, consulte la sección anterior sobre las funciones from_cx y to_cx.

Tomar un proyecto de C++/WinRT y agregar compatibilidad con C++/CX

En esta sección se describe qué hacer si ha decidido crear un nuevo proyecto de C++/WinRT y realizar el trabajo de portabilidad allí.

Para mezclar C++/WinRT y C++/CX en un proyecto de C++/WinRT, lo que incluye usar las funciones auxiliares from_cx y to_cx en el proyecto, tendrá que agregar manualmente soporte para C++/CX al proyecto.

  • Cree un nuevo proyecto de C++/WinRT en Visual Studio con una de las plantillas de proyecto de C++/WinRT (consulte Soporte de Visual Studio para C++/WinRT).
  • Active la compatibilidad del proyecto con C++/CX. En las propiedades del proyecto, C/C++>General>Consumir la extensión de Windows Runtime>Sí (/ZW).

Un proyecto de C++/WinRT de ejemplo que muestra las dos funciones auxiliares en uso

En esta sección, puede crear un proyecto de C++/WinRT de ejemplo que muestre cómo usar from_cx y to_cx. También muestra cómo se pueden usar alias de espacio de nombres para las diferentes islas de código, con el fin de gestionar posibles colisiones de espacios de nombres que, de otro modo, podrían producirse entre la proyección de C++/WinRT y la proyección de C++/CX.

  • Cree un proyecto de Visual C++>Windows>Universal Core App (C++/WinRT).
  • En las propiedades del proyecto, C/C++>General>Consumir extensión de Windows Runtime>Sí (/ZW).
  • Agregue interop_helpers.h al proyecto. Para consultar instrucciones sobre cómo hacerlo, consulte la sección anterior sobre las funciones from_cx y to_cx.
  • Reemplaza el contenido de App.cpp por el código siguiente.
  • Compile y ejecute.

WINRT_ASSERT es una definición de macro y se expande a _ASSERTE.

// App.cpp
#include "pch.h"
#include <sstream>

namespace cx
{
    using namespace Windows::Foundation;
}

namespace winrt
{
    using namespace Windows;
    using namespace Windows::ApplicationModel::Core;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Numerics;
    using namespace Windows::UI;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::Composition;
}

struct App : winrt::implements<App, winrt::IFrameworkViewSource, winrt::IFrameworkView>
{
    winrt::CompositionTarget m_target{ nullptr };
    winrt::VisualCollection m_visuals{ nullptr };
    winrt::Visual m_selected{ nullptr };
    winrt::float2 m_offset{};

    winrt::IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(winrt::CoreApplicationView const &)
    {
    }

    void Load(winrt::hstring const&)
    {
    }

    void Uninitialize()
    {
    }

    void Run()
    {
        winrt::CoreWindow window = winrt::CoreWindow::GetForCurrentThread();
        window.Activate();

        winrt::CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(winrt::CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(winrt::CoreWindow const & window)
    {
        winrt::Compositor compositor;
        winrt::ContainerVisual root = compositor.CreateContainerVisual();
        m_target = compositor.CreateTargetForCurrentView();
        m_target.Root(root);
        m_visuals = root.Children();

        window.PointerPressed({ this, &App::OnPointerPressed });
        window.PointerMoved({ this, &App::OnPointerMoved });

        window.PointerReleased([&](auto && ...)
        {
            m_selected = nullptr;
        });
    }

    void OnPointerPressed(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        winrt::float2 const point = args.CurrentPoint().Position();

        for (winrt::Visual visual : m_visuals)
        {
            winrt::float3 const offset = visual.Offset();
            winrt::float2 const size = visual.Size();

            if (point.x >= offset.x &&
                point.x < offset.x + size.x &&
                point.y >= offset.y &&
                point.y < offset.y + size.y)
            {
                m_selected = visual;
                m_offset.x = offset.x - point.x;
                m_offset.y = offset.y - point.y;
            }
        }

        if (m_selected)
        {
            m_visuals.Remove(m_selected);
            m_visuals.InsertAtTop(m_selected);
        }
        else
        {
            AddVisual(point);
        }
    }

    void OnPointerMoved(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        if (m_selected)
        {
            winrt::float2 const point = args.CurrentPoint().Position();

            m_selected.Offset(
            {
                point.x + m_offset.x,
                point.y + m_offset.y,
                0.0f
            });
        }
    }

    void AddVisual(winrt::float2 const point)
    {
        winrt::Compositor compositor = m_visuals.Compositor();
        winrt::SpriteVisual visual = compositor.CreateSpriteVisual();

        static winrt::Color colors[] =
        {
            { 0xDC, 0x5B, 0x9B, 0xD5 },
            { 0xDC, 0xED, 0x7D, 0x31 },
            { 0xDC, 0x70, 0xAD, 0x47 },
            { 0xDC, 0xFF, 0xC0, 0x00 }
        };

        static unsigned last = 0;
        unsigned const next = ++last % _countof(colors);
        visual.Brush(compositor.CreateColorBrush(colors[next]));

        float const BlockSize = 100.0f;

        visual.Size(
        {
            BlockSize,
            BlockSize
        });

        visual.Offset(
        {
            point.x - BlockSize / 2.0f,
            point.y - BlockSize / 2.0f,
            0.0f,
        });

        m_visuals.InsertAtTop(visual);

        m_selected = visual;
        m_offset.x = -BlockSize / 2.0f;
        m_offset.y = -BlockSize / 2.0f;
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    winrt::init_apartment();

    winrt::Uri uri(L"http://aka.ms/cppwinrt");
    std::wstringstream wstringstream;
    wstringstream << L"C++/WinRT: " << uri.Domain().c_str() << std::endl;

    // Convert from a C++/WinRT type to a C++/CX type.
    cx::Uri^ cx = to_cx<cx::Uri>(uri);
    wstringstream << L"C++/CX: " << cx->Domain->Data() << std::endl;
    ::OutputDebugString(wstringstream.str().c_str());

    // Convert from a C++/CX type to a C++/WinRT type.
    winrt::Uri uri_from_cx = from_cx<winrt::Uri>(cx);
    WINRT_ASSERT(uri.Domain() == uri_from_cx.Domain());
    WINRT_ASSERT(uri == uri_from_cx);

    winrt::CoreApplication::Run(winrt::make<App>());
}

API importantes