Migrar a C++/WinRT desde C++/CX

Este tema es el primero en una serie que describe cómo puede migrar el código fuente del proyecto de C++/CX a su equivalente en C++/WinRT.

Si su proyecto también usa tipos de Windows Runtime C++ Template Library (WRL), consulte Pasar de WRL a C++/WinRT.

Estrategias para la portabilidad

Vale la pena saber que la migración de C++/CX a C++/WinRT es generalmente sencilla, con la única excepción de pasar de tareas de biblioteca de patrones paralelos (PPL) a corrutinas. Los modelos son diferentes. No existe una correspondencia natural biunívoca entre las tareas de PPL y las corrutinas, y no hay una forma sencilla de portar mecánicamente el código de una manera que funcione en todos los casos. Para obtener ayuda sobre este aspecto concreto de la migración y sobre las opciones de interoperabilidad entre los dos modelos, consulta Asincronía e interoperabilidad entre C++/WinRT y C++/CX.

Los equipos de desarrollo informan rutinariamente de que una vez que superan el obstáculo de migrar su código asincrónico, el resto del trabajo de portabilidad es en gran medida mecánico.

Migración de una sola vez

Si puede migrar el proyecto completo de una sola vez, solo necesitará este tema para obtener la información necesaria (y no necesitará los temas sobre interoperabilidad que vienen a continuación). Te recomendamos que empieces creando un nuevo proyecto en Visual Studio usando una de las plantillas de proyecto de C++/WinRT (consulta Visual Studio compatibilidad con C++/WinRT). A continuación, mueva los archivos de código fuente a ese nuevo proyecto y migre todo el código fuente de C++/CX a C++/WinRT al hacerlo.

Como alternativa, si prefiere realizar el trabajo de portabilidad en el proyecto de C++/CX existente, deberá agregarle compatibilidad con C++/WinRT. Los pasos que sigues para hacerlo se describen en Tomar un proyecto de C++/CX y agregar compatibilidad con C++/WinRT. Cuando haya terminado de migrar, habrá convertido lo que era un proyecto de C++/CX puro en un proyecto de C++/WinRT puro.

Note

Si tiene un proyecto de componente para Windows Runtime, su única opción es realizar la migración de una sola vez. Un proyecto de componente de Windows Runtime escrito en C++ debe contener todo el código fuente de C++/CX o todo el código fuente de C++/WinRT. No pueden coexistir en este tipo de proyecto.

Migración gradual de un proyecto

A excepción de los proyectos de componentes de Windows Runtime, como se mencionó en la sección anterior, si el tamaño o la complejidad del código base hace necesario migrar el proyecto gradualmente, necesitará un proceso de portabilidad en el que, durante un tiempo, el código de C++/CX y C++/WinRT existe en paralelo en el mismo proyecto. Además de leer este tema, consulte también Interoperabilidad entre C++/WinRT y C++/CX y Asincronía e interoperabilidad entre C++/WinRT y C++/CX. Estos temas proporcionan información y ejemplos de código que muestran cómo interoperar entre las dos proyecciones de lenguaje.

Para preparar un proyecto para un proceso de portabilidad gradual, una opción es agregar compatibilidad con C++/WinRT al proyecto de C++/CX. Los pasos que sigues para hacerlo se describen en Tomar un proyecto de C++/CX y agregar compatibilidad con C++/WinRT. Después, puede migrar gradualmente desde allí.

Otra opción es crear un nuevo proyecto en Visual Studio mediante una de las plantillas de proyecto de C++/WinRT (consulte Visual Studio compatibilidad con C++/WinRT). A continuación, agregue compatibilidad con C++/CX a ese proyecto. Los pasos que sigue para hacerlo se describen en Tomar un proyecto de C++/WinRT y agregar compatibilidad con C++/CX. A continuación, puedes empezar a mover el código fuente hacia eso y migrar parte del código fuente de C++/CX a C++/WinRT como lo haces.

En cualquier caso, podrás interoperar (en ambos sentidos) entre tu código en C++/WinRT y cualquier código en C++/CX que todavía no hayas portado.

Note

Tanto C++/CX como el SDK de Windows declaran tipos en el espacio de nombres raíz Windows. Un tipo de Windows proyectado en C++/WinRT tiene el mismo nombre completo que el tipo de Windows, pero se coloca en el espacio de nombres winrt de C++. Estos espacios de nombres distintos le permiten migrar de C++/CX a C++/WinRT a su propio ritmo.

Migración gradual de un proyecto XAML

Important

Para un proyecto que usa XAML, en cualquier momento todos los tipos de página XAML deben ser completamente C++/CX o completamente C++/WinRT. Aún puedes mezclar C++/CX y C++/WinRT fuera de los tipos de página XAML en el mismo proyecto (en tus modelos y modelos de vista, y en otras partes).

En este escenario, el flujo de trabajo que se recomienda es crear un nuevo proyecto de C++/WinRT y copiar código fuente y marcado desde el proyecto de C++/CX. Siempre que todos los tipos de página XAML sean C++/WinRT, puedes agregar nuevas páginas XAML con Project>Agregar nuevo elemento...>Visual C++>Página en blanco (C++/WinRT).

Como alternativa, puedes usar un componente de Windows Runtime (WRC) para extraer código del proyecto XAML de C++/CX durante la migración.

  • Puedes crear un nuevo proyecto WRC de C++/CX, mover a ese proyecto tanto código de C++/CX como puedas y, a continuación, cambiar el proyecto XAML a C++/WinRT.
  • O bien, podrías crear un nuevo proyecto WRC de C++/WinRT, dejar el proyecto XAML como C++/CX y empezar a migrar de C++/CX a C++/WinRT, trasladando el código resultante del proyecto XAML al proyecto del componente.
  • También podría tener un proyecto de componente de C++/CX junto con un proyecto de componente de C++/WinRT dentro de la misma solución, hacer referencia a ambos desde el proyecto de aplicación y migrar gradualmente de uno a otro. De nuevo, consulta Interoperabilidad entre C++/WinRT y C++/CX para obtener más información sobre el uso de las dos proyecciones de lenguaje en el mismo proyecto.

Primeros pasos para migrar un proyecto de C++/CX a C++/WinRT

Independientemente de cuál sea la estrategia de portabilidad (portar en un paso o portar gradualmente), el primer paso es preparar el proyecto para la portabilidad. Este es un resumen de lo que se describe en Estrategias para la portabilidad en términos del tipo de proyecto con el que empezará y cómo configurarlo.

  • Adaptación de una sola pasada. Cree un nuevo proyecto en Visual Studio con una de las plantillas de proyecto de C++/WinRT. Mueva los archivos del proyecto de C++/CX a ese nuevo proyecto y porte el código fuente de C++/CX.
  • Migrar un proyecto que no es XAML gradualmente. Puedes elegir agregar compatibilidad con C++/WinRT al proyecto de C++/CX (consulta Tomar un proyecto de C++/CX y agregar compatibilidad con C++/WinRT) y migrar gradualmente. También puedes crear un nuevo proyecto de C++/WinRT y agregar compatibilidad con C++/CX a ese proyecto (consulta Tomar un proyecto de C++/WinRT y agregar compatibilidad con C++/CX), mover archivos y migrar gradualmente.
  • Migrar un proyecto XAML gradualmente. Cree un nuevo proyecto de C++/WinRT, mueva archivos y realice la migración gradualmente. En un momento dado, los tipos de las páginas XAML deben ser o bien todos C++/WinRT o bien todos C++/CX.

El resto de este tema se aplica independientemente de la estrategia de portabilidad que elija. Contiene un catálogo de detalles técnicos implicados en la migración del código fuente de C++/CX a C++/WinRT. Si vas a migrar gradualmente, es probable que también quieras consultar Interoperabilidad entre C++/WinRT y C++/CX y La asincronía y la interoperabilidad entre C++/WinRT y C++/CX.

Convenciones de nomenclatura de archivos

Archivos de marcado XAML

Origen del archivo C++/CX C++/WinRT
Archivos XAML para desarrolladores MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (consulte a continuación)
Archivos XAML generados MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

Tenga en cuenta que C++/WinRT elimina el .xaml de los nombres de archivo *.h y *.cpp.

C++/WinRT agrega un archivo de desarrollador adicional, el archivo Midl (.idl). C++/CX genera automáticamente este archivo internamente y lo agrega a todos los miembros públicos y protegidos. En C++/WinRT, agrega y crea el archivo usted mismo. Para obtener más información, ejemplos de código y un tutorial de creación de IDL, consulta Controles XAML; enlazar a una propiedad C++/WinRT.

Consulte también Incorporación de clases de tiempo de ejecución en archivos MIDL (.idl)

Clases de tiempo de ejecución

C++/CX no impone restricciones en los nombres de los archivos de encabezado; Es habitual colocar varias definiciones de clase en tiempo de ejecución en un único archivo de encabezado, especialmente para clases pequeñas. Pero C++/WinRT requiere que cada clase en tiempo de ejecución tenga su propio archivo de encabezado denominado después del nombre de clase.

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

Menos común (pero todavía legal) en C++/CX es usar archivos de encabezado con nombre diferente para controles personalizados XAML. Tendrá que cambiar el nombre de este archivo de encabezado para que coincida con el nombre de clase.

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace implements {
  struct LongNameForA { ... };
}

Requisitos del archivo de encabezado

C++/CX no requiere que incluya ningún archivo de encabezado especial, ya que genera internamente archivos de encabezado a partir de .winmd archivos. Es habitual en C++/CX usar las directivas using para espacios de nombres a los que se hace referencia por su nombre.

using namespace Windows::Media::Playback;

String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
    return item->VideoTracks->GetAt(0)->Name;
}

La using namespace Windows::Media::Playback directiva nos permite escribir MediaPlaybackItem sin un prefijo de espacio de nombres. También hemos modificado el espacio de nombres Windows.Media.Core, porque item->VideoTracks->GetAt(0) devuelve un Windows.Media.Core.VideoTrack. Pero no teníamos que escribir el nombre VideoTrack en ningún lugar, por lo que no necesitamos una using Windows.Media.Core directiva.

Pero C++/WinRT te obliga a incluir un archivo de cabecera correspondiente a cada espacio de nombres que utilices, aunque no lo menciones.

#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!

using namespace winrt;
using namespace Windows::Media::Playback;

winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
    return item.VideoTracks().GetAt(0).Name();
}

Por otro lado, aunque el evento MediaPlaybackItem.AudioTracksChanged sea de tipo TypedEventHandler<MediaPlaybackItem, Windows. Foundation.Collections.IVectorChangedEventArgs>, no es necesario incluirlo winrt/Windows.Foundation.Collections.h porque no usamos ese evento.

C++/WinRT también requiere que incluyas archivos de encabezado para espacios de nombres consumidos por el marcado XAML.

<!-- MainPage.xaml -->
<Rectangle Height="400"/>

Usar la clase Rectangle implica que hay que añadir esta directiva include.

// MainPage.h
#include <winrt/Microsoft.UI.Xaml.Shapes.h>

Si olvida un archivo de cabecera, todo se compilará correctamente, pero obtendrá errores del enlazador porque faltan las clases consume_.

Paso de parámetros

Al escribir código fuente de C++/CX, se pasan tipos de C++/CX como parámetros de función como referencias de hat (^).

void LogPresenceRecord(PresenceRecord^ record);

En C++/WinRT, para las funciones sincrónicas, debería usar parámetros const& de forma predeterminada. Esto evitará copias e sobrecarga interbloqueada. Pero sus corrutinas deben usar el paso por valor para garantizar que se capturen por valor y evitar problemas de tiempo de vida (para obtener más información, consulte Concurrencia y operaciones asincrónicas con C++/WinRT).

void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);

Un objeto C++/WinRT es fundamentalmente un valor que contiene un puntero de interfaz al objeto subyacente de Windows Runtime. Al copiar un objeto C++/WinRT, el compilador copia el puntero de interfaz encapsulado, incrementando su recuento de referencias. La eventual destrucción de la copia implica disminuir el recuento de referencias. Por lo tanto, asume el coste de hacer una copia solo cuando sea necesario.

Referencias de variables y campos

Al escribir código fuente de C++/CX, se usan variables hat (^) para hacer referencia a objetos Windows Runtime y el operador de flecha (->) para desreferenciar una variable hat.

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

Al migrar al código equivalente de C++/WinRT, puede avanzar mucho quitando los símbolos de intercalación (^) y sustituyendo el operador de flecha (->) por el operador punto (.). Los tipos proyectados de C++/WinRT son valores y no punteros.

IVectorView<User> userList = User::Users();

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
    ...

El constructor predeterminado de una referencia de hat de C++/CX lo inicializa en null. Este es un ejemplo de código de C++/CX en el que se crea una variable o un campo del tipo correcto, pero uno sin inicializar. En otras palabras, no hace referencia inicialmente a un TextBlock; tenemos intención de asignar una referencia más adelante.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Para obtener el equivalente en C++/WinRT, consulte Inicialización retrasada.

Propiedades

Las extensiones de lenguaje C++/CX incluyen el concepto de propiedades. Al escribir código fuente de C++/CX, puede acceder a una propiedad como si fuera un campo. C++ estándar no tiene el concepto de una propiedad, por lo que, en C++/WinRT, se llama a las funciones get y set.

En los ejemplos siguientes, XboxUserId, UserState, PresenceDeviceRecords y Size son todas las propiedades.

Obtener un valor de una propiedad

Aquí se muestra cómo obtener un valor de propiedad en C++/CX.

void Sample::LogPresenceRecord(PresenceRecord^ record)
{
    auto id = record->XboxUserId;
    auto state = record->UserState;
    auto size = record->PresenceDeviceRecords->Size;
}

El código fuente equivalente de C++/WinRT llama a una función con el mismo nombre que la propiedad, pero sin parámetros.

void Sample::LogPresenceRecord(PresenceRecord const& record)
{
    auto id = record.XboxUserId();
    auto state = record.UserState();
    auto size = record.PresenceDeviceRecords().Size();
}

Tenga en cuenta que la función PresenceDeviceRecords devuelve un objeto Windows Runtime que tiene una función Size. Como el objeto devuelto también es un tipo proyectado de C++/WinRT, desreferenciamos mediante el operador dot para llamar a Size.

Establecer una propiedad a un nuevo valor

Asignar una propiedad a un nuevo valor sigue un patrón similar. En primer lugar, en C++/CX.

record->UserState = newValue;

Para realizar el equivalente en C++/WinRT, llame a una función con el mismo nombre que la propiedad y pase un argumento.

record.UserState(newValue);

Crear una instancia de una clase

Trabaja con un objeto de C++/CX a través de un identificador para él, comúnmente conocido como referencia de sombrero (^). Se crea un nuevo objeto a través de la ref new palabra clave , que a su vez llama a RoActivateInstance para activar una nueva instancia de la clase en tiempo de ejecución.

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

Un objeto C++/WinRT es un valor, así que puede alojarlo en la pila o como campo de un objeto. Nunca se usa ref new (ni new) para asignar un objeto C++/WinRT. Entre bastidores, se sigue llamando a RoActivateInstance.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
private:
    Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};

Si un recurso es costoso de inicializar, es habitual retrasar la inicialización hasta que realmente sea necesario. Como ya se ha mencionado, el constructor predeterminado de una referencia hat de C++/CX la inicializa a null.

using namespace Windows::Storage::Streams;

class Sample
{
public:
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer^ m_gamerPicBuffer;
};

El mismo código migrado a C++/WinRT. Tenga en cuenta el uso del constructor std::nullptr_t . Para obtener más información sobre ese constructor, consulte Inicialización retrasada.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

Cómo afecta el constructor predeterminado a las colecciones

Los tipos de colección de C++ usan el constructor predeterminado, lo que puede dar lugar a la construcción de objetos no deseados.

Scenario C++/CX C++/WinRT (incorrecto) C++/WinRT (correcto)
Variable local, inicialmente vacía TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
Variable miembro, inicialmente vacía class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
Variable global, inicialmente vacía TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
Vector de referencias vacías std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
Establecer un valor en un mapa std::map<int, TextBox^> boxes;
boxes[2] = value;
std::map<int, TextBox> boxes;
// Creates a TextBox at 2,
// then overwrites it!
boxes[2] = value;
std::map<int, TextBox> boxes;
boxes.insert_or_assign(2, value);
Matriz de referencias vacías TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Par std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

Más información sobre colecciones de referencias vacías

Siempre que tenga una Platform::Array^ (consulte Port Platform::Array^) en C++/CX, tiene la opción de convertirla en un std::vector en C++/WinRT (de hecho, cualquier contenedor contiguo), en lugar de dejarla como un array. Hay ventajas para elegir std::vector.

Por ejemplo, aunque hay una abreviatura para crear un vector de tamaño fijo de referencias vacías (consulte la tabla anterior), no existe tal abreviatura para crear un array de referencias vacías. Debe repetir nullptr para cada elemento de una matriz. Si tiene demasiados, los extras se construirán de forma predeterminada.

Para un vector, puede rellenarlo con referencias vacías en la inicialización (como en la tabla anterior) o puede rellenarla con referencias vacías posteriores a la inicialización con código como este.

std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.

Más información sobre el ejemplo std::map

El [] operador de subíndice para std::map se comporta de la siguiente manera.

  • Si la clave se encuentra en el mapa, devuelva una referencia al valor existente (que puede sobrescribir).
  • Si la clave no se encuentra en el mapa, cree una nueva entrada en el mapa que consta de la clave (movida, si es extraíble) y un valor construido de forma predeterminada y devuelva una referencia al valor (que luego puede sobrescribir).

En otras palabras, el [] operador siempre crea una entrada en el mapa. Esto es diferente de C#, Java y JavaScript.

Conversión de una clase en tiempo de ejecución base a una derivada

Es habitual tener una referencia a una base que sepa que hace referencia a un objeto de un tipo derivado. En C++/CX, se usa dynamic_cast para convertir la referencia a base en una referencia a derivada. Es dynamic_cast realmente una llamada oculta a QueryInterface. Aquí tienes un ejemplo típico: estás manejando un evento de cambio de una propiedad de dependencia y quieres convertir DependencyObject de nuevo al tipo real que posee la propiedad de dependencia.

void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject^ d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
    BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };

    if (theControl != nullptr)
    {
        // succeeded ...
    }
}

El código de C++/WinRT equivalente reemplaza por dynamic_cast una llamada a la función IUnknown::try_as , que encapsula QueryInterface. También puede optar por llamar a IUnknown::as, que genera una excepción si la consulta de la interfaz requerida (la interfaz predeterminada del tipo que está solicitando) no devuelve ningún resultado. Este es un ejemplo de código de C++/WinRT.

void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // succeeded ...
    }

    try
    {
        BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
        // succeeded ...
    }
    catch (winrt::hresult_no_interface const&)
    {
        // failed ...
    }
}

Clases derivadas

Para derivar de una clase en tiempo de ejecución, la clase base debe ser composable. C++/CX no requiere que realice ningún paso especial para que las clases se puedan componer, pero C++/WinRT sí. Usa la palabra clave unsealed para indicar que quieres que tu clase se pueda usar como clase base.

unsealed runtimeclass BasePage : Microsoft.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

En la clase de encabezado de implementación, debe incluir el archivo de encabezado de clase base antes de incluir el encabezado generado automáticamente para la clase derivada. De lo contrario, obtendrá errores como "Uso no válido de este tipo como expresión".

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

Manejo de eventos con un delegado

Este es un ejemplo típico de controlar un evento en C++/CX mediante una función lambda como delegado en este caso.

auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Este es el equivalente en C++/WinRT.

auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

En lugar de una función lambda, puede optar por implementar el delegado como una función libre o como una función de puntero a miembro. Para obtener más información, consulta Controlar eventos mediante delegados en C++/WinRT.

Si vas a migrar desde un código base de C++/CX en el que los eventos y delegados se usan internamente (no entre archivos binarios), winrt::d elegate te ayudará a replicar ese patrón en C++/WinRT. Consulte también Delegados con parámetros, señales simples y funciones de devolución de llamada en un proyecto.

Revocar un delegado

En C++/CX se usa el -= operador para revocar un registro de eventos anterior.

myButton->Click -= token;

Este es el equivalente en C++/WinRT.

myButton().Click(token);

Para obtener más información y opciones, consulta Revocar un delegado registrado.

Boxing y unboxing

C++/CX encapsula automáticamente los valores escalares en objetos. C++/WinRT requiere que llames a la función winrt::box_value explícitamente. Ambos lenguajes requieren que desempaquetes explícitamente. Consulta Boxing y unboxing con C++/WinRT.

En las tablas siguientes, usaremos estas definiciones.

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
Operation C++/CX C++/WinRT
Boxeo o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Desempaquetado i = (int)o;
s = (String^)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX y C# generan excepciones si intenta desenboxar un puntero nulo a un tipo de valor. C++/WinRT considera esto un error de programación y se bloquea. En C++/WinRT, use la función winrt::unbox_value_or si desea controlar el caso en el que el objeto no es del tipo que pensaba que era.

Scenario C++/CX C++/WinRT
Desencapsular un entero conocido i = (int)o; i = unbox_value<int>(o);
Si o es null Platform::NullReferenceException Choque
Si o no es un int encapsulado Platform::InvalidCastException Choque
Desempaquetar int, usar un valor alternativo si es null; fallar si es cualquier otra cosa i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Desencapsular int si es posible; usar una alternativa para cualquier otro caso auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Boxing y unboxing de una cadena

Una cadena es de alguna manera un tipo de valor y de otras maneras un tipo de referencia. C++/CX y C++/WinRT tratan cadenas de forma diferente.

El tipo ABI HSTRING es un puntero a una cadena con recuento de referencias. Pero no se deriva de IInspectable, por lo que no es técnicamente un objeto . Además, un HSTRING nulo representa la cadena vacía. El boxing de elementos que no derivan de IInspectable se realiza encapsulándolos en un IReference<T>, y Windows Runtime proporciona una implementación estándar en forma de objeto PropertyValue (los tipos personalizados se indican como PropertyType::OtherType).

C++/CX representa una cadena de Windows Runtime como un tipo de referencia; mientras que C++/WinRT proyecta una cadena como un tipo de valor. Esto significa que una cadena nula encapsulada puede tener distintas representaciones según cómo se haya obtenido.

Además, C++/CX permite desreferenciar un valor String^nulo, en cuyo caso se comporta como la cadena "".

Behavior C++/CX C++/WinRT
Declaraciones Object^ o;
String^ s;
IInspectable o;
hstring s;
Categoría de tipo de cadena Tipo de referencia Tipo de valor
null HSTRING se proyecta como (String^)nullptr hstring{}
¿Son null y "" idénticos? Yes Yes
Validez de null s = nullptr;
s->Length == 0 (válido)
s = hstring{};
s.size() == 0 (válido)
Si asigna una cadena nula al objeto o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
Si asigna "" al objeto o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr

Boxing y unboxing básicos.

Operation C++/CX C++/WinRT
Encapsular una cadena o = s;
La cadena vacía se convierte en nullptr.
o = box_value(s);
La cadena vacía se convierte en un objeto no NULL.
Desboxe una cadena conocida s = (String^)o;
El objeto NULL se convierte en una cadena vacía.
InvalidCastException si no se trata de una cadena.
s = unbox_value<hstring>(o);
Se bloquea el objeto NULL.
Falla si no es una cadena.
Desencadene una posible cadena s = dynamic_cast<String^>(o);
Un objeto null o un valor que no sea una cadena se convierte en una cadena vacía.
s = unbox_value_or<hstring>(o, fallback);
Un valor nulo o que no sea una cadena se convierte en el valor predeterminado.
Cadena vacía conservada.

Operaciones simultáneas y asincrónicas

La biblioteca de patrones paralelos (PPL) (concurrency::task, por ejemplo) se actualizó para admitir referencias de sombrero de C++/CX.

En C++/WinRT, debería usar corrutinas y co_await en su lugar. Para obtener más información y ejemplos de código, consulta Operaciones asincrónicas y simultaneidad con C++/WinRT.

Consumo de objetos del marcado XAML

En un proyecto de C++/CX, puedes usar miembros privados y elementos con nombre definido en el marcado XAML. Pero en C++/WinRT, todas las entidades consumidas mediante la extensión de marcado XAML {x:Bind} deben exponerse públicamente en IDL.

Además, la vinculación a un valor booleano muestra true o false en C++/CX, pero muestra Windows.Foundation.IReference`1<Boolean> en C++/WinRT.

Para obtener más información y ejemplos de código, consulte Consumo de objetos de marcado.

Asignación de los tipos Platform de C++/CX a los tipos de C++/WinRT

C++/CX proporciona varios tipos de datos en el espacio de nombres platform . Estos tipos no son estándar de C++, por lo que solo puede usarlos al habilitar extensiones de lenguaje Windows Runtime (Visual Studio propiedad del proyecto C/C++>General>Consume Windows Runtime Extensión>Sí (/ZW)). La tabla siguiente le ayuda a migrar desde tipos de plataforma a sus equivalentes en C++/WinRT. Una vez hecho esto, ya que C++/WinRT es C++estándar, puede desactivar la /ZW opción.

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ Véase Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Migrar Platform::Agile^ a winrt::agile_ref

El tipo Platform::Agile^ de C++/CX representa una clase Windows Runtime a la que se puede acceder desde cualquier subproceso. El equivalente de C++/WinRT es winrt::agile_ref.

En C++/CX.

Platform::Agile<Windows::UI::Core::CoreWindow> m_window;

En C++/WinRT (WinUI 3 usa Microsoft::UI::Xaml::Window en lugar de CoreWindow).

winrt::agile_ref<Microsoft::UI::Xaml::Window> m_window;

Portar Platform::Array^

En los casos en los que C++/CX requiere usar una matriz, C++/WinRT permite usar cualquier contenedor contiguo. Vea Cómo afecta el constructor predeterminado a las colecciones por un motivo por el que std::vector es una buena opción.

Por lo tanto, siempre que tenga un Platform::Array^ en C++/CX, sus opciones para la migración incluyen usar una lista de inicialización, un std::array o un std::vector. Para obtener más información y ejemplos de código, consulte Listas de inicializadores estándar y Matrices y vectores estándar.

Migrar Platform::Exception^ a winrt::hresult_error

El tipo Platform::Exception^ se genera en C++/CX cuando una API de Windows Runtime devuelve un valor HRESULT que no es de S_OK. El equivalente de C++/WinRT es winrt::hresult_error.

Para migrar a C++/WinRT, cambie todo el código que usa Platform::Exception^ para usar winrt::hresult_error.

En C++/CX.

catch (Platform::Exception^ ex)

En C++/WinRT.

catch (winrt::hresult_error const& ex)

C++/WinRT proporciona estas clases de excepción.

Tipo de excepción Clase base HRESULT
winrt::hresult_error llame a hresult_error::to_abi
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED
winrt::hresult_canceled winrt::hresult_error ERROR_CANCELLED
winrt::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE
winrt::hresult_illegal_delegate_assignment winrt::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT
winrt::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL
winrt::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE
winrt::hresult_invalid_argument winrt::hresult_error E_INVALIDARG
winrt::hresult_no_interface winrt::hresult_error E_NOINTERFACE
winrt::hresult_not_implemented winrt::hresult_error E_NOTIMPL
winrt::hresult_out_of_bounds winrt::hresult_error E_BOUNDS
winrt::hresult_wrong_thread winrt::hresult_error RPC_E_WRONG_THREAD

Tenga en cuenta que cada clase (a través de la clase base hresult_error ) proporciona una función to_abi , que devuelve el HRESULT del error y una función de mensaje , que devuelve la representación de cadena de ese HRESULT.

Aquí tienes un ejemplo de cómo lanzar una excepción en C++/CX.

throw ref new Platform::InvalidArgumentException(L"A valid User is required");

Y el equivalente en C++/WinRT.

throw winrt::hresult_invalid_argument{ L"A valid User is required" };

Adaptar Platform::Object^ a winrt::Windows::Foundation::IInspectable

Al igual que todos los tipos de C++/WinRT, winrt::Windows::Foundation::IInspectable es un tipo de valor. Aquí se muestra cómo inicializar una variable de ese tipo en NULL.

winrt::Windows::Foundation::IInspectable var{ nullptr };

Migre Platform::String^ a winrt::hstring

Platform::String^ es equivalente al tipo ABI de Windows Runtime HSTRING. Para C++/WinRT, el equivalente es winrt::hstring. Sin embargo, con C++/WinRT, puede llamar a las API de Windows Runtime usando tipos de cadenas anchas de la biblioteca estándar de C++, como std::wstring, o literales de cadena ancha. Para obtener más información y ejemplos de código, consulte Control de cadenas en C++/WinRT.

Con C++/CX, puede acceder a la propiedad Platform::String::Data para obtener la cadena como una cadena de estilo C const wchar_t* (por ejemplo, para pasarla a std::wcout).

auto var{ titleRecord->TitleName->Data() };

Para hacer lo mismo con C++/WinRT, puede usar la función hstring::c_str para obtener una versión de cadena de estilo C terminada en null, igual que puede hacerlo desde std::wstring.

auto var{ titleRecord.TitleName().c_str() };

Cuando se trata de implementar API que toman o devuelven cadenas, normalmente se cambia cualquier código de C++/CX que use Platform::String^ para usar winrt::hstring en su lugar.

Este es un ejemplo de una API de C++/CX que toma una cadena.

void LogWrapLine(Platform::String^ str);

Para C++/WinRT, podría declarar esa API en MIDL 3.0 como esta.

// LogType.idl
void LogWrapLine(String str);

A continuación, la cadena de herramientas de C++/WinRT generará código fuente para usted que tenga este aspecto.

void LogWrapLine(winrt::hstring const& str);

ToString()

Los tipos de C++/CX proporcionan el método Object::ToString .

int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".

C++/WinRT no proporciona directamente esta instalación, pero puedes recurrir a alternativas.

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

C++/WinRT también admite winrt::to_hstring para un número limitado de tipos. Tendrás que añadir sobrecargas para cualquier tipo adicional que quieras convertir en cadena.

Language Convertir int en cadena Convertir enumeración en cadena
C++/CX String^ result = "hello, " + intValue.ToString(); String^ result = "status: " + status.ToString();
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

En el caso de convertir una enumeración en cadena, deberá proporcionar la implementación de winrt::to_hstring.

namespace winrt
{
    hstring to_hstring(StatusEnum status)
    {
        switch (status)
        {
        case StatusEnum::Success: return L"Success";
        case StatusEnum::AccessDenied: return L"AccessDenied";
        case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
        default: return to_hstring(static_cast<int>(status));
        }
    }
}

Estas conversiones a cadena a menudo se consumen implícitamente mediante el enlace de datos.

<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>

Estas vinculaciones realizarán la conversión de la propiedad enlazada mediante winrt::to_hstring. En el caso del segundo ejemplo (StatusEnum), debe proporcionar su propia sobrecarga de winrt::to_hstring; de lo contrario, obtendrá un error del compilador.

Construcción de cadenas

C++/CX y C++/WinRT recurren al std::wstringstream estándar para crear cadenas.

Operation C++/CX C++/WinRT
Agregar cadena, conservando nulos stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Añadir cadena, detenerse al encontrar el primer carácter nulo stream << s->Data(); stream << s.c_str();
Extraer resultado ws = stream.str(); ws = stream.str();

Más ejemplos

En los ejemplos siguientes, ws es una variable de tipo std::wstring. Además, aunque C++/CX puede construir una platform::String a partir de una cadena de 8 bits, C++/WinRT no lo hace.

Operation C++/CX C++/WinRT
Construcción de una cadena a partir de literales String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Conversión de std::wstring, conservando valores NULL String^ s = ref new String(ws.c_str(),
  (uint32_t)ws.size());
winrt::hstring s{ ws };
s = winrt::hstring(ws);
// s = ws; // Doesn't compile
Convertir desde std::wstring, detenerse en el primer carácter nulo String^ s = ref new String(ws.c_str()); winrt::hstring s{ ws.c_str() };
s = winrt::hstring(ws.c_str());
// s = ws.c_str(); // Doesn't compile
Conversión a std::wstring, conservando valores NULL std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
Convertir a std::wstring, detener en el primer valor NULL std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Pasar literal al método Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Pasar std::wstring al método Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

API importantes

Note

Muchos temas de C++/WinRT están en proceso de migrarse de la documentación de UWP a esta sección. Hasta que se complete la migración, los vínculos de la lista siguiente pueden dar lugar a la sección de documentos de UWP. La proyección del lenguaje C++/WinRT es la misma para las aplicaciones para UWP y WinUI 3, de modo que el contenido sea aplicable en ambos contextos. Los patrones propios de UWP (como el ciclo de vida de la aplicación o las API del espacio de nombres Windows.UI) se indican explícitamente en esos artículos.