Compartir a través de


Migrar de C++/CX a C++/WinRT

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

Si el proyecto también usa tipos de biblioteca de plantillas de C++ de Windows Runtime (WRL), consulta Mover a C++/WinRT desde WRL.

Estrategias para el traspaso

Vale la pena saber que la migración de C++/CX a C++/WinRT suele ser sencilla, con la única excepción de pasar de biblioteca de patrones paralelos (PPL) tareas a corrutinas. Los modelos son diferentes. No hay una asignación natural uno a uno de las tareas de PPL a corrutinas y no hay ninguna manera sencilla de portar mecánicamente el código que funciona para todos los casos. Para obtener ayuda con este aspecto específico de la portabilidad y las opciones para interoperar entre los dos modelos, consulte 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.

Traslado en un paso

Si está en una posición para poder portar todo el proyecto de una sola vez, solo necesitará este tema para obtener la información que necesita (y no necesitará los temas de interoperabilidad que vienen después de este). Se recomienda empezar por crear un nuevo proyecto en Visual Studio con una de las plantillas de proyecto de C++/WinRT (consulte Compatibilidad de Visual Studio con C++/WinRT). A continuación, mueva los archivos de código fuente a ese nuevo proyecto y porte todo el código fuente de C++/CX a C++/WinRT a medida que lo hace.

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 sigue 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.

Nota:

Si tiene un proyecto de componente de Windows Runtime, la portabilidad en un pase es la única opción. 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 de tu código base hace que sea necesario migrar el proyecto gradualmente, necesitarás un proceso de portabilidad en el que durante un tiempo C++/CX y código 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 sigue 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 Compatibilidad de Visual Studio 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 a ese proyecto y portar algunos del código fuente de C++/CX a C++/WinRT mientras lo haces.

En cualquier caso, interoperará en ambas direcciones entre su código de C++/WinRT y cualquier código de C++/CX que no haya migrado aún.

Nota:

Tanto de 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 winrt. 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

Importante

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. Todavía puedes mezclar C++/CX y C++/WinRT fuera de de tipos de página XAML dentro del 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 Proyecto>Agregar nuevo elemento...>Visual C++>Página en blanco (C++/WinRT).

Como alternativa, puedes usar un componente de Windows Runtime (WRC) para extraer el código del proyecto XAML de C++/CX a medida que lo portas.

  • Puedes crear un nuevo proyecto WRC de C++/CX, mover tanto código de C++/CX como puedas a ese proyecto y, a continuación, convertir el proyecto XAML para utilizar C++/WinRT.
  • O bien, podrías crear un nuevo proyecto WRC de C++/WinRT, dejar el proyecto XAML como C++/CX y comenzar a migrar de C++/CX a C++/WinRT, trasladando el código resultante fuera del proyecto XAML y al proyecto de 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 describimos en Estrategias para migrar en cuanto al tipo de proyecto con el que empezarás y cómo configurarlo.

  • Migración en un paso. 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.
  • Portar un proyecto XAML gradualmente. Cree un nuevo proyecto de C++/WinRT, mueva archivos y haga la migración de manera gradual. En cualquier momento, los tipos de página XAML deben todos los de C++/WinRT o todos los 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, probablemente también querrás ver Interoperabilidad entre C++/WinRT y C++/CX y Asincronía e 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 de 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

Observe que C++/WinRT quita los .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 de C++/WinRT.

Consulte también Factorizar clases de tiempo de ejecución en archivos Midl (.idl)

Clases en 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 de .winmd archivos de encabezado. Es habitual que C++/CX use directivas using para los espacios de nombres que consume por 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 tocado 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 requiere que incluyas un archivo de encabezado que corresponde a cada namespace que consumes, incluso si no lo nombras.

#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 incluir 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"/>

El uso de la clase Rectangle significa que tiene que añadir esta instrucción include.

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

Si olvida un archivo de encabezado, todo se compilará bien, pero encontrará errores del enlazador porque las clases de consume_ faltan.

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 funciones sincrónicas, debe usar de forma predeterminada los parámetros const&. Esto evitará copias e sobrecarga interbloqueada. Pero las corrutinas deben usar pass-by-value para asegurarse de que capturan por valor y evitan problemas de duración (para obtener más detalles, consulte operaciones de simultaneidad y 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 hacia el objeto de respaldo 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, solo asume el costo adicional de hacer una copia cuando sea necesario.

Referencias de variables y campos

Al escribir código fuente de C++/CX, usa variables hat (^) para hacer referencia a objetos de 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 de C++/WinRT equivalente, puede avanzar mucho eliminando los ^ y cambiando el operador de flecha (->) por el operador de 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 ver 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.

Recuperación de 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 de 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.

Configuración de una propiedad a un nuevo valor

Establecer una propiedad en 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; por tanto, puede declararlo en la pila o como un campo de un objeto. Nunca usar ref new (ni new) para asignar un objeto C++/WinRT. En segundo plano, 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 mencionó, el constructor predeterminado de una referencia de sombrero de C++/CX lo inicializa en 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.

Escenario 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 un Platform::Array^ (vea Port Platform::Array^) en C++/CX, tiene la opción de, en lugar de dejarlo como una matriz, migrarlo a un std::vector en C++/WinRT (de hecho, a cualquier contenedor contiguo). Hay ventajas para elegir std::vector.

Por ejemplo, aunque hay una abreviatura para crear un vector de tamaño fijo de referencias vacías (vea la tabla anterior), no hay ninguna forma abreviada para crear una matriz de referencias vacías. Debe repetir nullptr para cada elemento de una matriz. Si tiene muy pocos, entonces 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. Este es un ejemplo típico: estás manejando un evento de cambio de propiedad de dependencia y quieres convertir de DependencyObject al tipo real que posee la propiedad de dependencia.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::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 tiene la opción de llamar a IUnknown::asen su lugar y que genera una excepción cuando no se devuelve la consulta de la interfaz necesaria (la interfaz predeterminada del tipo que solicita). Este es un ejemplo de código de C++/WinRT.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::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 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í. Utiliza la palabra clave sin sellar para indicar que desea que la clase se pueda usar como una clase base.

unsealed runtimeclass BasePage : Windows.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 Gestionar eventos con 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 parametrizados, señales simples y callbacks dentro de 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 escalares en objetos. C++/WinRT requiere que llames explícitamente a la función winrt::box_value. Ambos idiomas requieren que desempaquete 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;
Operación 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 que esto es un error de programación, y el programa 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.

Escenario 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 entero encapsulado Platform::InvalidCastException Choque
Desempaqueta int, usa alternativa si es null; fallo si ocurre cualquier otra cosa i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Desempaquetar int si es posible; utilizar la opción alternativa para cualquier otra cosa auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Empaquetado y desempaquetado 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 técnicamente no es un objeto . Además, un HSTRING nulo representa la cadena vacía. La encapsulación de elementos que no se derivan de IInspectable se realiza al encapsularlos dentro de un IReference<T>, y Windows Runtime proporciona una implementación estándar en forma del objeto PropertyValue (los tipos personalizados se notifican como PropertyType::OtherType).

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

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

Comportamiento 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 proyectos como (String^)nullptr hstring{}
¿Son null y "" idénticos?
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

Conceptos básicos de empaquetado y desempaquetado.

Operación 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);
El objeto NULL provoca un fallo.
Se produce un error si no es una cadena.
Desempaquete una posible cadena s = dynamic_cast<String^>(o);
Un objeto nulo o uno que no es cadena se convierte en una cadena vacía.
s = unbox_value_or<hstring>(o, fallback);
Nulo o no cadena de texto se convierte en alternativa.
Cadena vacía conservada.

Simultaneidad y operaciones asincrónicas

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

Para C++/WinRT, debe usar corrutinas y co_await en su lugar. Para obtener más información y ejemplos de código, consulta Concurrencia y Operaciones Asincrónicas con C++/WinRT.

Consumo de objetos del marcado XAML

En un proyecto de C++/CX, puedes consumir miembros privados y elementos con nombre desde el marcado XAML. Sin embargo, en C++/WinRT, todas las entidades consumidas al usar la extensión de marcado {x:Bind} de XAML deben ser expuestas públicamente en la IDL.

Además, el enlace a un Booleano muestra true o false en C++/CX, pero muestra Windows.Foundation.IReference`1<Booleano> en C++/WinRT.

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

Asignación de tipos de de C++/CX Platform a 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 puedes usarlos al habilitar extensiones de lenguaje de Windows Runtime (propiedad del proyecto de Visual Studio C/C++>General>Consumir extensión> de Windows RuntimeSí (/ZW)). La tabla siguiente le ayuda a migrar desde los 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
Plataforma::Ágil^ winrt::agile_ref
Platform::Array^ Consulte Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Port Platform::Agile^ to winrt::agile_ref

El tipo Platform::Agile^ de C++/CX representa una clase de Windows Runtime a la que se puede tener acceso 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.

winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;

Plataforma de puerto::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.

Cada vez que tengas una Platform::Array^ en C++/CX, puedes optar por usar una lista de inicializadores, 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 arrays y vectores estándar.

Puerto Platform::Exception^ para 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 llamar a hresult_error::to_abi
acceso_denegado_winrt::hresult winrt::hresult_error E_ACCESSDENIED
winrt::hresult_cancelado winrt::hresult_error ERROR_CANCELADO
winrt::hresult_changed_state winrt::hresult_error E_CAMBIO_DE_ESTADO
winrt::hresult_class_not_available winrt::hresult_error CLASE_E_CLASENODISPONIBLE
winrt::hresult_illegal_delegate_assignment winrt::hresult_error E_ASIGNACIÓN_DELEGADO_ILEGAL
winrt::hresult_illegal_method_call winrt::hresult_error E_LLAMADA_DE_MÉTODO_ILEGAL
winrt::hresult_illegal_state_change winrt::hresult_error E_CAMBIO_DE_ESTADO_ILEGAL
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 mensaje, que devuelve la representación en cadena de ese HRESULT.

Este es un ejemplo de 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" };

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

Port Platform::String^ a winrt::hstring

Platform::String^ es equivalente al tipo ABI HSTRING de Windows Runtime. Para C++/WinRT, el equivalente es winrt::hstring. Pero con C++/WinRT, puedes llamar a las API de Windows Runtime mediante 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::D ata para recuperar la cadena como un de estilo C wchar_t* matriz (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 agregar sobrecargas para cualquier tipo adicional que quieras convertir a cadena.

Lenguaje Stringify int Convertir enumeración a 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 en cadena una enumeración, 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 transformaciones en cadena a menudo son consumidas 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 ejecutarán winrt::to_hstring de la propiedad enlazada. En el caso del segundo ejemplo (el StatusEnum), debe proporcionar su propia sobrecarga de winrt::to_hstring; de lo contrario, obtendrá un error del compilador.

Creación de cadenas

C++/CX y C++/WinRT recurren al estándar std::wstringstream para la creación de cadenas.

Operación C++/CX C++/WinRT
Anexar cadena, conservando valores NULL stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Agregar cadena, detener en el primer 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.

Operación 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
Convierte desde std::wstring, detente 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;
Convierta en std::wstring, detenga 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