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

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

Si el proyecto también está usando tipos de Windows Runtime C++ Template Library (WRL) (Biblioteca de plantillas C++ de Windows Runtime [WRL]), consulta Move to C++/WinRT from WRL (Migración de C++/WinRT desde WRL).

Estrategias de portabilidad

Merece la pena saber que la portabilidad de C++/CX a C++/WinRT suele ser sencilla, con la única excepción de mover desde las tareas de la Biblioteca de patrones paralelos (PPL) a corrutinas. Los modelos son diferentes. No hay una asignación natural de uno a uno de las tareas de PPL en corrutinas, y no hay ninguna manera sencilla de portar el código de forma mecánica que funcione en todos los casos. Para obtener ayuda con este aspecto específico de la portabilidad y las opciones de interoperabilidad entre los dos modelos, consulte Asincronía e interoperabilidad entre C++/WinRT y C++/CX.

Los equipos de desarrollo informan de forma rutinaria que, una vez que superan el obstáculo de portabilidad de su código asincrónico, el resto del trabajo de portabilidad es principalmente mecánico.

Portabilidad en un solo paso

Si está en posición de poder portar todo el proyecto en un solo paso, solo necesitará este tema para obtener la información que necesita (y no necesitará los temas de interoperabilidad que se encuentran a continuación). Se recomienda comenzar por 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, mueva los archivos de código fuente a ese nuevo proyecto y porte 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, tendrá que agregarle compatibilidad con C++/WinRT. Los pasos que debe seguir para ello se describen en Adopción de un proyecto de C++/CX y adición de compatibilidad con C++/WinRT. Para cuando termine de portar, habrá convertido lo que era un proyecto puramente de C++/CX en un proyecto puramente de C++/WinRT.

Nota

Si tiene un proyecto de componentes de Windows Runtime, la portabilidad en un solo paso es su única opción. Un proyecto de componentes de Windows Runtime escrito en C++ debe contener todo el código fuente en C++/CX o todo el código fuente en C++/WinRT. No pueden coexistir en este tipo de proyecto.

Portabilidad gradual de un proyecto

Con la excepción de 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 hacen necesario portar el proyecto gradualmente, necesitará un proceso de portabilidad en el que, en un momento, exista código de C++/CX y C++/WinRT 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. En estos temas se proporcionan información y ejemplos de código que muestran cómo interoperar entre las proyecciones de los dos lenguajes.

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 debe seguir para ello se describen en Adopción de un proyecto de C++/CX y adición de compatibilidad con C++/WinRT. Después puede portar 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). Y, a continuación, agregue compatibilidad con C++/CX a ese proyecto. Los pasos que debe seguir para ello se describen en Adopción de un proyecto de C++/WinRT y adición de compatibilidad con C++/CX. A continuación, empiece a mover el código fuente a él y porte parte del código fuente de C++/CX a C++/WinRT al hacerlo.

En todo caso, interoperará (de ambas maneras) entre el código de C++/WinRT y el código de C++/CX que todavía no haya portado.

Nota

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 totalmente cualificado que el tipo de Windows, pero se coloca en el espacio de nombres winrt de C++. Estos espacios de nombres diferentes te permiten migrar de C++/CX a C++/WinRT a tu propio ritmo.

Portabilidad gradual de un proyecto XAML

Importante

Para un proyecto que use XAML, en un momento dado, todos los tipos de página XAML deben ser completamente C++/CX o completamente C++/WinRT. Todavía puede combinar C++/CX y C++/WinRT fuera de los tipos de página XAML dentro del mismo proyecto (en los modelos, modelos de vista y en cualquier otro lugar).

En este escenario, el flujo de trabajo que se recomienda es crear un nuevo proyecto de C++/WinRT y copiar el código fuente y el marcado del proyecto de C++/CX. Siempre y cuando todos los tipos de páginas XAML sean C++/WinRT, puede 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 factorizar el código fuera del proyecto XAML de C++/CX al portarlo.

  • Puede crear un nuevo proyecto de WRC de C++/CX, mover la mayor cantidad del código de C++/CX como sea posible a ese proyecto y, a continuación, cambiar el proyecto XAML a C++/WinRT.
  • O bien, puede crear un nuevo proyecto de WRC de C++/WinRT, dejar el proyecto XAML como C++/CX, y empezar a portar el código de C++/CX a C++/WinRT y mover el código resultante fuera del proyecto XAML al proyecto de componentes.
  • También puedes tener un proyecto de componente C++/CX junto con uno de C++/WinRT en la misma solución, hacer referencia a ambos desde el proyecto de aplicación y migrar gradualmente de uno al otro. Nuevamente, consulte Interoperabilidad entre C++/WinRT y C++/CX para obtener más detalles sobre el uso de proyecciones de dos lenguajes en el mismo proyecto.

Primeros pasos en la portabilidad de un proyecto C++/CX a C++/WinRT

Sea cual sea su estrategia de portabilidad (portabilidad en un paso o portabilidad gradual), el primer paso es preparar el proyecto para la portabilidad. A continuación se muestra un resumen de lo que se describe en Estrategias de portabilidad en cuanto al tipo de proyecto con el que empezará y cómo configurarlo.

  • Portabilidad en un solo 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.
  • Portabilidad gradual de un proyecto no XAML Puede optar por agregar compatibilidad con C++/WinRT a su proyecto de C++/CX (consulte Adopción de un proyecto de C++/CX y adición de compatibilidad con C++/WinRT) y portarlo gradualmente. También puede optar por crear un nuevo proyecto de C++/WinRT y agregarle compatibilidad con C++/CX (consulte Adopción de un proyecto de C++/WinRT y adición de compatibilidad con C++/CX), migrar los archivos y portar gradualmente.
  • Portabilidad gradual de un proyecto XAML Cree un nuevo proyecto de C++/WinRT, mueva los archivos y porte gradualmente. En un momento dado, los tipos de página XAML deben estar, o bien todos en C++/WinRT, o bien todos en 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 relacionados con la portabilidad del código fuente de C++/CX a C++/WinRT. Si va a portar gradualmente, probablemente también quiera consultar 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 de 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 (ver 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

Ten en cuenta que C++/WinRT quita .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 de manera interna y lo agrega a todos los miembros públicos y protegidos. En C++/WinRT, puedes agregar y crear el archivo tú mismo. Para más detalles, ejemplos de código y un tutorial de creación de IDL, consulta Controles de XAML; enlazar a una propiedad de C++/WinRT.

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

Clases en tiempo de ejecución

C++/CX no impone restricciones sobre 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, en especial en el caso de las clases pequeñas. Pero C++/WinRT requiere que el nombre de archivo de encabezado de cada clase en tiempo de ejecución aparezca 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 (aunque sigue siendo válido) en C++/CX es usar archivos de encabezado con nombres diferentes para los controles personalizados de XAML. Tendrás que cambiar el nombre de este archivo de encabezado para que coincida con el nombre de la 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 de archivos de encabezado

C++/CX no requiere que incluyas ningún archivo de encabezado especial, ya que de forma interna genera automáticamente archivos de encabezado a partir de archivos .winmd. En C++/CX es habitual usar directivas de using para los espacios de nombres que consumes por nombre.

using namespace Windows::Media::Playback;

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

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

Sin embargo, C++/WinRT te exige incluir un archivo de encabezado que se corresponda con cada espacio de nombres que consumes, incluso aunque no lo nombres.

#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 es de tipo TypedEventHandler<MediaPlaybackItem, Windows.Foundation.Collections.IVectorChangedEventArgs>, no es necesario incluir winrt/Windows.Foundation.Collections.h, porque no usamos ese evento.

Además, C++/WinRT requiere que incluyas los archivos de encabezado para los espacios de nombres que consume el marcado XAML.

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

El uso de la clase Rectangle significa que tienes que agregar este include.

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

Si olvidas un archivo de encabezado, todo se compilará correctamente, pero obtendrá errores del enlazador, ya que faltan las clases consume_.

Paso de parámetros

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

void LogPresenceRecord(PresenceRecord^ record);

En C++/WinRT, para las funciones sincrónicas, debes usar los parámetros const& de manera predeterminada. Eso evitará copias y sobrecarga entrelazada. Pero tus corrutinas deben usar paso-por-valor para garantizar que capturan por valor y evitar los problemas de ciclo de vida (para más información, consulta Operaciones simultáneas y asincrónicas con C++/WinRT).

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

Un objeto de C++/WinRT es esencialmente un valor que contiene un puntero a interfaz para el objeto de Windows Runtime de copia de seguridad. Cuando copias un objeto de C++/WinRT, el compilador copia el puntero a interfaz encapsulado, lo cual incrementa su recuento de referencia. La destrucción final de la copia conlleva la disminución del recuento de referencia. Por lo tanto, incurre solo en la sobrecarga de una copia cuando sea necesario.

Referencias de variables y campos

Al escribir código fuente de C++/CX, usa variables de circunflejo (^) para hacer referencia a objetos de Windows Runtime y el operador de flecha (->) para desreferenciarlas.

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

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

Al trasladar código al equivalente C++/WinRT, puedes perder mucho tiempo quitando los acentos circunflejos y cambiando el operador de flecha (->) al punto (.). Los tipos proyectados de C++/ WinRT son valores, no punteros.

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

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

El constructor predeterminado para una referencia de acento circunflejo 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 que se no inicializa. En otras palabras, inicialmente no hace referencia a TextBlock; pensamos asignar una referencia más adelante.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Para el equivalente en C++/WinRT, consulta Delayes initialization (Demora en la inicialización).

Propiedades

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

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

Recuperar un valor de una propiedad

Esta es la manera de 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 de C++/WinRT equivalente 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();
}

Ten 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 de punto para llamar a Size.

Establecer una propiedad en un valor nuevo

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

record->UserState = newValue;

Para hacer lo equivalente en C++/WinRT, llama a una función con el mismo nombre que la propiedad y pasa un argumento.

record.UserState(newValue);

Crear una instancia de una clase

Trabaja con un objeto de C++/CX mediante un manipulador que se conoce comúnmente como referencia de circunflejo (^). Crea un objeto mediante la palabra clave ref new, que a su vez llama a RoActivateInstance para activar una instancia nueva 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 de C++/WinRT es un valor, por lo que puede asignarlo en la pila o como campo de un objeto. No uses nuncaref new (ni new) para asignar un objeto de 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 caro de inicializar, es habitual retrasar su inicialización hasta que sea realmente necesario. Como ya se ha mencionado, el constructor predeterminado para una referencia de acento circunflejo 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. Ten en cuenta el uso del constructor std::nullptr_t. Para más información acerca del constructor, consulta Inicialización demorada.

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

De qué manera el constructor predeterminado afecta 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 imprevistos.

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 };
Pareja 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 las colecciones de referencias vacías

Siempre que tenga Platform::Array^ (consulte Migración de Platform::Array^) en C++/CX, tendrá la opción de migrar a std::vector en C++/WinRT (de hecho, cualquier contenedor contiguo) en lugar de dejarlo como matriz. Elegir std::vector tiene sus ventajas.

Por ejemplo, si bien hay una forma abreviada de crear un vector de tamaño fijo de referencias vacías (consulta la tabla anterior), no existe tal forma abreviada para crear una matriz de referencias vacías. Tienes que repetir nullptr para cada elemento de una matriz. Si no tiene suficientes, los adicionales se construirán de forma predeterminada.

En el caso de un vector, puedes rellenarlo con referencias vacías en la inicialización (como en la tabla anterior), o puedes rellenarlo con referencias vacías después de 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 de std::map

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

  • Si la clave se encuentra en el mapa, devuelve una referencia al valor existente (que puedes sobrescribir).
  • Si la clave no se encuentra en el mapa, crea una nueva entrada en el mapa que conste de la clave (movida, si es movible) y un valor construido de forma predeterminada, y devuelve una referencia al valor (que luego puedes 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 base en tiempo de ejecución a una derivada

Es habitual tener una referencia a base que sabes 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 derivado. dynamic_cast es simplemente una llamada oculta a QueryInterface. Este es un ejemplo típico: está controlando un evento con cambio de propiedad de dependencia y quiere convertir 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 dynamic_cast por una llamada a la función IUnknown::try_as que encapsula QueryInterface. También tienes la opción de llamar a IUnknown::as en su lugar, lo que produce una excepción si no se devuelve la consulta de la interfaz necesaria (la interfaz predeterminada del tipo que está solicitando). 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 a partir de una clase en tiempo de ejecución, la clase base debe admitir la composición. C++/CX no requiere que sigas ningún paso en particular para hacer que estas clases admitan composición, pero C++/WinRT sí. Usa la palabra clave unsealed para indicar que quieres que la clase se pueda usar como clase base.

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

En la clase de encabezado de tu implementación, tienes que incluir el archivo de encabezado de la clase base antes de incluir el encabezado generado automáticamente para la clase derivada. De lo contrario, obtendrás 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>
    {
        ...
    }
}

Control de eventos con un delegado

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

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, puedes elegir implementar tu delegado como función libre o como función de puntero a miembro. Para más información, consulta Control de eventos mediante delegados en C++/WinRT.

Si vas a portar desde una base de código de C++/CX donde se usan internamente eventos y delegados (no archivos binarios), winrt::delegate te ayudará a replicar ese patrón en C++/WinRT. Consulta también Parameterized delegates, simple signals, and callbacks within a project (Delegados con parámetros, señales simples y devoluciones de llamada dentro de un proyecto).

Revocación de un delegado

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

myButton->Click -= token;

Este es el equivalente en C++/WinRT.

myButton().Click(token);

Para más información y opciones, consulta Revoke a registered delegate (Revocación de un delegado registrado).

Conversiones boxing y unboxing

C++/CX aplica automáticamente boxing a los valores escalares para convertirlos en objetos. C++/WinRT requiere que llames a la función winrt::box_value de manera explícita. Ambos lenguajes requieren que apliques la conversión unboxing de manera explícita. Consulta Conversión 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
Boxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Unboxing i = (int)o;
s = (String^)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX y C# producen excepciones si intentas aplicar unboxing para convertir un puntero nulo en a un tipo de valor. C++/WinRT considera que se trata de un error de programación y se bloquea. En C++/WinRT, usa la función winrt::unbox_value_or si quieres controlar el caso en el que el objeto no sea del tipo que pensabas que era.

Escenario C++/CX C++/WinRT
Conversión unboxing de un entero conocido i = (int)o; i = unbox_value<int>(o);
Si o es null Platform::NullReferenceException Bloqueo
Si o no es un entero con conversión boxing Platform::InvalidCastException Bloqueo
Unboxing de entero, usar reserva si es null; bloquear en cualquier otro caso i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Unboxing de entero, si es posible; usar reversión en cualquier otro caso auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Conversiones boxing y unboxing en una cadena

En cierto modo, una cadena es un tipo de valor y, de otro modo, un tipo de referencia. C++/CX y C++/WinRT tratan las cadenas de manera diferente.

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

C++/CX representa una cadena de Windows Runtime como tipo de referencia; mientras que C++/WinRT proyecta una cadena como tipo de valor. Es decir, una cadena nula a la que se ha aplicado la conversión boxing puede tener distintas representaciones en función de cómo llegue allí.

Además, C++/CX te permite desreferenciar una String^ null, 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
HSTRING null se proyecta 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 asignas una cadena null a un objeto o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
Si asignas "" a un objeto o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr

Conversiones boxing y unboxing básicas.

Operación C++/CX C++/WinRT
Aplicar boxing a una cadena o = s;
Una cadena vacía se convierte en nullptr.
o = box_value(s);
Una cadena vacía se convierte en un objeto con un valor distinto de null.
Unboxing a una cadena conocida s = (String^)o;
El objeto null se convierte en una cadena vacía.
InvalidCastException si no es una cadena.
s = unbox_value<hstring>(o);
Un objeto null se bloquea.
Se bloquea si no es una cadena.
Conversión unboxing de una posible cadena s = dynamic_cast<String^>(o);
Un objeto null o que no es una cadena se convierte en una cadena vacía.
s = unbox_value_or<hstring>(o, fallback);
Un valor null o que no es una cadena se convierte en un elemento Fallback.
Una cadena vacía se conserva.

Operaciones simultáneas y asincrónicas

La biblioteca de patrones de procesamiento paralelo (PPL) (concurrency::task, por ejemplo) se actualizó para admitir las referencias de acento circunflejo de C++/CX.

En el caso de C++/WinRT, debes usar las corrutinas y co_await en su lugar. Para obtener más información y ejemplos de código, consulta Operaciones simultáneas y asincrónicas con C++/WinRT.

Consumir objetos a partir del marcado XAML

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

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

Para más información y ejemplos de código, consulta Consumir objetos a partir del marcado.

Asignación de tipos Plataforma de C++/CX a C++/WinRT

C++/CX proporciona varios tipos de datos en el espacio de nombres Plataforma. Estos tipos no son de la versión C++ estándar, por lo que solo puedes usarlos al habilitar extensiones de lenguaje de Windows Runtime (propiedad de proyecto de Visual Studio C/C++>General>Usar extensión de Windows Runtime>Sí (/ZW) ). La tabla siguiente ayuda a migrar desde tipos Plataforma a sus equivalentes en C++/WinRT. Una vez que lo hayas hecho, puesto que C++/WinRT es C++ estándar, puedes desactivar la opción /ZW.

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ Consulte Migración de Platform::Array
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Migración de Platform::Agile^ a winrt::agile_ref

La escritura de Platform::Agile^ en C++/CX representa una clase de 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.

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

Migración de Platform::Array^

En los casos donde C++/CX necesita usar una matriz, C++/WinRT te permite usar cualquier contenedor contiguo. Consulta De qué manera el constructor predeterminado afecta a las colecciones para ver el motivo por el que std::vector es una buena elección.

Por lo tanto, siempre que tenga Platform::Array^ en C++/CX, las opciones de portabilidad incluyen el uso de una lista de inicializadores, un valor std::array o un valor std::vector. Para más información y ejemplos de código, consulta Standard initializer lists (Listas de inicializadores estándar) y Standard arrays and vectors (Vectores y matrices estándar).

Migración de Platform::Exception^ a winrt::hresult_error

El tipo Platform::Exception^ se produce en C++/CX cuando una API de Windows Runtime devuelve un HRESULT que no es 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 que use 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 llamada 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

Ten en cuenta que cada clase (mediante la clase base hresult_error) proporciona una función to_abi que devuelve el HRESULT del error, y una función message que devuelve la representación de la cadena de ese HRESULT.

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

Migración de Platform::Object^ a winrt::Windows::Foundation::IInspectable

Como todos los tipos de C++/WinRT, winrt::Windows::Foundation::IInspectable es un tipo de valor. Aquí te mostramos cómo inicializar una variable de ese tipo en null.

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

Migración de Platform::String^ a winrt::hstring

Platform::String^ equivale 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 de caracteres anchos de la biblioteca estándar de C++ como std::wstring o literales de cadena de caracteres anchos. Para más información y ejemplos de código, consulta Control de cadenas en C++/WinRT.

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

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

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

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

En cuanto a la implementación de las API que toman o devuelven cadenas, por lo general cambias cualquier código de C++/CX que use Platform::String^ para que use 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ías declarar esa API en MIDL 3.0 así.

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

La cadena de herramientas de C++/WinRT generará entonces código fuente con este aspecto automáticamente.

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 ofrece directamente esta función, pero puedes elegir 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 los tipos adicionales a los que quieras aplicar stringify.

Language Stringify int Stringify enum
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 caso de aplicar stringify en una enumeración, debes 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));
        }
    }
}

El enlace de datos suele consumir implícitamente estas aplicaciones de stringify.

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

Estos enlaces ejecutarán winrt::to_hstring de la propiedad enlazada. En el caso del segundo ejemplo (StatusEnum), debes proporcionar tu propia sobrecarga de winrt::to_hstring; de lo contrario, recibirás un error del compilador.

Creación de cadenas

C++/CX y C++/WinRT se aplazan a std::wstringstream estándar 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 };
Anexar cadena, detener en el primer valor null 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, mientras que C++/CX puede construir Platform::String a partir de una cadena de 8 bits, C++/WinRT no puede.

Operación C++/CX C++/WinRT
Construir una cadena a partir de un literal String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Convertir 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 de std::wstring, detener en el primer valor null 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
Convertir 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 un literal a un método Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Pasar std::wstring a un 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