Migrar a C++/WinRT desde C#

Sugerencia

Si ha leído este tema antes y lo vuelve a consultar para una tarea determinada, puede dirigirse a la sección Buscar contenido en función de la tarea que está realizando de este tema.

En este tema se catalogan de forma exhaustiva los detalles técnicos implicados en la migración del código fuente de un proyecto de C# a su equivalente de C++/WinRT.

Para ver un caso práctico de migración de uno de los ejemplos de aplicaciones de la Plataforma universal de Windows (UWP), consulta el tema complementario Migración del ejemplo de Clipboard a C++/WinRT desde C#. Para ganar práctica y experiencia en la migración, puedes seguir ese tutorial y portar el ejemplo por tu cuenta a medida que avanzas.

Cómo realizar la preparación y qué esperar

En el caso práctico del tema Migración del ejemplo de Clipboard a C++/WinRT desde C#, se muestran ejemplos de los tipos de decisiones de diseño de software que deberás tomar al portar un proyecto a C++/WinRT. Por lo tanto, te recomendamos que, para prepararte para la migración, obtengas conocimientos detallados sobre cómo funciona el código existente. De este modo, contarás con información general adecuada sobre la funcionalidad de la aplicación y la estructura del código y, luego, las decisiones que tomes siempre te ayudarán a avanzar en la dirección adecuada.

En lo que se refiere a los tipos de cambio en la migración que debes esperar, puedes agruparlos en cuatro categorías.

  • Migración de la proyección del lenguaje. Windows Runtime (WinRT) se proyecta en varios lenguajes de programación. Cada una de esas proyecciones de lenguaje está diseñada para que suene idiomática en el lenguaje de programación en cuestión. En C#, algunos tipos de Windows Runtime se proyectan como tipos .NET. Por ejemplo, traducirás System.Collections.Generic.IReadOnlyList<T> de nuevo a Windows.Foundation.Collections.IVectorView<T>. También en C#, algunas operaciones de Windows Runtime se proyectan como características de lenguaje adecuadas de C#. Un ejemplo sería el uso de la sintaxis de operador += en C# para registrar un delegado de control de eventos. Por lo tanto, traducirás características del lenguaje; por ejemplo, para volver a la operación fundamental que se está realizando (el registro de eventos, en este caso).
  • Migración de la sintaxis del lenguaje. Muchos de estos cambios son transformaciones mecánicas simples, en las que se reemplaza un símbolo por otro. Por ejemplo, se cambia un punto (.) por dos signos de dos puntos (::).
  • Procedimiento de migración del lenguaje. Algunos de estos pueden ser cambios sencillos y repetitivos (como de myObject.MyProperty a myObject.MyProperty()). Otros requieren cambios más complejos (por ejemplo, portar un procedimiento que implique el uso de System.Text.StringBuilder a uno que implique el uso de std::wostringstream).
  • Tareas relacionadas con la portación específicas de C++/WinRT. C# se encarga de completar determinados detalles de Windows Runtime de manera implícita y en segundo plano. Estos detalles se realizan explícitamente en C++/WinRT. Un ejemplo sería el uso de un archivo .idl para definir las clases en tiempo de ejecución.

Después del índice basado en tareas que aparece a continuación, el resto de las secciones de este tema se estructuran según la taxonomía anterior.

Buscar contenido en función de la tarea que se está realizando

Tarea Content
Crear un componente de Windows Runtime (WRC) Se puede lograr cierta funcionalidad (o llamadas a algunas API) solo con C++. Puede factorizar esa funcionalidad en un WRC de C++/WinRT y, a continuación, consumir el WRC desde una aplicación de C#, por ejemplo. Consulte Componentes de Windows Runtime con C++/WinRT y Si vas a crear una clase en tiempo de ejecución en un componente de Windows Runtime.
Portar un método asincrónico Es una buena idea que la primera línea de un método asincrónico de una clase de C++/WinRT en tiempo de ejecución sea auto lifetime = get_strong(); (consulte Acceso de forma segura al puntero this en una corrutina de miembro de clase).

Para la portabilidad desde Task, consulte Acción asincrónica.
Para la portabilidad desde Task<T>, consulte Operación asincrónica.
Para la portabilidad desde async void, consulte Método Fire-and-forget.
Portal una clase En primer lugar, determine si la clase debe ser una clase en tiempo de ejecución o si puede ser una clase ordinaria. Para que le resulte más fácil tomar esta decisión, consulte el principio de Crear API con C++/WinRT. A continuación, consulte las tres filas siguientes.
Portar una clase en tiempo de ejecución Una clase que comparte la funcionalidad fuera de la aplicación de C++ o una clase que se usa en el enlace de datos XAML. Consulte Si vas a crear una clase en tiempo de ejecución en un componente de Windows Runtime o Si vas a crear una clase en tiempo de ejecución a la que se hará referencia en tu interfaz de usuario de XAML.

Estos vínculos describen esto con más detalle, pero se debe una clase en tiempo de ejecución en el IDL. Si el proyecto ya contiene un archivo IDL (por ejemplo, Project.idl), se recomienda declarar cualquier nueva clase en tiempo de ejecución en ese archivo. En el IDL, declare los métodos y miembros de datos que se usarán fuera de la aplicación o que se usarán en XAML. Después de actualizar el archivo IDL, vuelva a compilarlo y examine los archivos de código auxiliar generados (.h y .cpp) en la carpeta Generated Files del proyecto (en el Explorador de soluciones, con el nodo del proyecto seleccionado, asegúrese de que Mostrar todos los archivos esté activado). Compare los archivos de código auxiliar con los archivos que ya están en el proyecto, y agregue archivos, o bien agregue o actualice firmas de función, según sea necesario. La sintaxis de los archivos de código auxiliar siempre es correcta, por lo que se recomienda utilizarla para minimizar los errores de compilación. Una vez que los códigos auxiliares del proyecto coinciden con los de los archivos de código auxiliar, puede continuar e implementarlos mediante la portabilidad del código de C# completo.
Portar una clase ordinaria Consulte Si no vas a crear una clase en tiempo de ejecución.
Crear un IDL Introducción al Lenguaje de definición de interfaz de Microsoft 3.0
Si vas a crear una clase en tiempo de ejecución a la que se hará referencia en tu interfaz de usuario de XAML
Consumir objetos a partir del marcado XAML
Definición de las clases en tiempo de ejecución en IDL
Portar una colección Colecciones con C++/WinRT
Poner un origen de datos a disposición del marcado XAML
Contenedor asociativo
Acceso de miembro vectorial
Portar un evento Delegado del controlador de eventos como miembro de clase
Delegado del controlador de eventos de revocación
Portar un método Desde C#: private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
Para el archivo de C++/WinRT .h: fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
Para el archivo de C++/WinRT .cpp: fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Portar cadenas Control de cadenas en C++/WinRT
ToString
String-building
Conversiones boxing y unboxing en una cadena
Conversión de tipos C#: o.ToString()
C++/WinRT: to_hstring(static_cast<int>(o))
Consulte también ToString.

C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
Se genera si se produce un error en la conversión unboxing. Consulte también Conversión boxing and unboxing.

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
Devuelve fallback si se produce un error en la conversión unboxing. Consulte también Conversión boxing and unboxing.

C#: (Class)o
C++/WinRT: o.as<Class>()
Se genera si se produce un error de conversión.

C#: o as Class
C++/WinRT: o.try_as<Class>()
Devuelve null si se produce un error de conversión.

Cambios relacionados con la proyección de lenguaje

Categoría C# C++/WinRT Consulta también
Objeto sin tipo object o System.Object Windows::Foundation::IInspectable Migración del método EnableClipboardContentChangedNotifications
Espacios de nombres de proyección using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Tamaño de una colección collection.Count collection.Size() Migración del método BuildClipboardFormatsOutputString
Tipo de colección típico IList<T> y Agregar para agregar un elemento. IVector<T> y Anexar para agregar un elemento. Si usa un elemento std::vector en alguna parte, emplee push_back para agregar un elemento.
Tipo de colección de solo lectura IReadOnlyList<T> IVectorView<T> Migración del método BuildClipboardFormatsOutputString
Delegado del controlador de eventos como miembro de clase myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Migración del método EnableClipboardContentChangedNotifications
Delegado del controlador de eventos de revocación myObject.EventName -= Handler; myObject.EventName(token); Migración del método EnableClipboardContentChangedNotifications
Contenedor asociativo IDictionary<K, V> IMap<K, V>
Acceso de miembro vectorial x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Registro/revocación de un controlador de eventos

En C++/WinRT, tienes varias opciones sintácticas para registrar o revocar un delegado del controlador de eventos, tal como se describe en Control de eventos mediante delegados en C++/WinRT. Consulta también el tema Migración del método EnableClipboardContentChangedNotifications.

A veces, por ejemplo, cuando el destinatario de un evento (un objeto que controla un evento) esté a punto de destruirse, te recomendamos que revoques un controlador de eventos para que el origen del evento (el objeto que provoca el evento) no llame a un objeto destruido. Consulta Revocación de un delegado registrado. En estos casos, crea una variable de miembro event_token para los controladores de eventos. Para ver un ejemplo, consulta el tema Migración del método EnableClipboardContentChangedNotifications.

También puedes registrar un controlador de eventos en el marcado XAML.

<Button x:Name="OpenButton" Click="OpenButton_Click" />

En C#, el método OpenButton_Click puede ser privado, y XAML aún podrá conectarlo al evento ButtonBase.Click generado por OpenButton.

En C++/WinRT, el método OpenButton_Click debe ser público en el tipo de implementaciónsi quiere registrarlo en el marcado XAML. Si solo registras un controlador de eventos en código imperativo, no es necesario que el controlador de eventos sea público.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

Como alternativa, puedes hacer que la página XAML de registro sea compatible con el tipo de implementación, y convertir el método OpenButton_Click en privado.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
    private:
        friend MyPageT;
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

Un escenario final es donde el proyecto de C# que estás portando se enlaza al controlador de eventos desde el marcado (para más información sobre este escenario, consulta Funciones de x:Bind).

<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />

Puedes cambiarlo al marcado Click="OpenButton_Click" más sencillo. O bien, si lo prefieres, puedes mantener el marcado tal como está. Todo lo que tienes que hacer para admitirlo es declarar el controlador de eventos en IDL.

void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);

Nota

Declara la función como void incluso si la implementas como Desencadenamiento y olvido.

Cambios relacionados con la sintaxis del lenguaje

Categoría C# C++/WinRT Consulta también
Modificadores de acceso public \<member\> public:
    \<member\>
Migración del método Button_Click
Acceso a un miembro de datos this.variable this->variable  
Acción asincrónica async Task ... IAsyncAction ... Interfaz IAsyncAction, Operaciones simultáneas y asincrónicas con C++/WinRT
Operación asincrónica async Task<T> ... IAsyncOperation<T> ... Interfaz IAsyncOperation, Operaciones simultáneas y asincrónicas con C++/WinRT
Método Fire-and-forget (implica una asincronía) async void ... winrt::fire_and_forget ... Portabilidad del método CopyButton_Click, Desencadenamiento y olvido
Acceso a una constante enumerada E.Value E::Value Migración del método DisplayChangedFormats
Espera cooperativa await ... co_await ... Migración del método CopyButton_Click
Colección de tipos proyectados como campo privado private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
Construcción de GUID private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
Separador de espacios de nombres A.B.T A::B::T
Null null nullptr Migración del método UpdateStatus
Obtención de un objeto type typeof(MyType) winrt::xaml_typename<MyType>() Migración de la propiedad Scenarios
Declaración de parámetros para un método MyType MyType const& Parameter-passing
Declaración de parámetros para un método asincrónico MyType MyType Parameter-passing
Llamada a un método estático T.Method() T::Method()
Cadenas string o System.String winrt::hstring Control de cadenas en C++/WinRT
Literal de cadena "a string literal" L"a string literal" Migración del constructor, Current y FEATURE_NAME
Tipo inferido (o deducido) var auto Migración del método BuildClipboardFormatsOutputString
Using-directive using A.B.C; using namespace A::B::C; Migración del constructor, Current y FEATURE_NAME
Literal de cadena sin formato/textual @"verbatim string literal" LR"(raw string literal)" Migración del método DisplayToast

Nota

Si un archivo de encabezado no contiene una directiva de using namespace para un espacio de nombres determinado, deberás calificar completamente todos los nombres de tipo de ese espacio de nombre, o por lo menos lo suficiente como para que el compilador los pueda encontrar. Para obtener un ejemplo, consulta Migración del método DisplayToast.

Migración de clases y miembros

Para cada tipo de C#, deberás decidir si quieres portarlo a un tipo de Windows Runtime o a una clase, estructura o enumeración de C++ normal. Para obtener más información y ejemplos detallados en los que se muestra cómo tomar esas decisiones, consulta el tema Migración del ejemplo de Clipboard a C++/WinRT desde C#.

Una propiedad de C#, normalmente, se convierte en una función de descriptor de acceso, una función mutadora y un miembro de datos de respaldo. Para obtener más información y un ejemplo, consulta el tema Migración de la propiedad IsClipboardContentChangedEnabled.

En el caso de los campos no estáticos, haz que sean miembros de datos del tipo de implementación.

Un campo estático de C# se convierte en una función mutadora o un descriptor de acceso estático de C++/WinRT. Para obtener más información y un ejemplo, consulta el tema Migración del constructor, Current y FEATURE_NAME.

En el caso de las funciones de miembro, de nuevo deberás decidir si cada una de ellas forma parte o no de IDL, o si se trata de una función de miembro pública o privada del tipo de implementación. Para obtener más información y ejemplos sobre cómo decidirlo, consulta el tema IDL para el tipo MainPage.

Migración del marcado XAML y los archivos de recursos

En el tema Migración del ejemplo de Clipboard a C++/WinRT desde C#, pudimos usar el mismo marcado XAML (incluidos los recursos) y los archivos de recursos en el proyecto de C# y de C++/WinRT. En algunos casos, se requerirán ediciones en el marcado para lograrlo. Consulta el tema Copia de XAML y estilos necesarios para terminar de portar MainPage.

Cambios que implican procedimientos del lenguaje

Categoría C# C++/WinRT Consulta también
Administración de la duración en un método asincrónico N/A auto lifetime{ get_strong() }; o
auto lifetime = get_strong();
Migración del método CopyButton_Click
Eliminación using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Migración del método CopyImage
Construcción del objeto new MyType(args) MyType{ args } o
MyType(args)
Migración de la propiedad Scenarios
Creación de una referencia no inicializada MyType myObject; MyType myObject{ nullptr }; o
MyType myObject = nullptr;
Migración del constructor, Current y FEATURE_NAME
Construcción de un objeto en una variable con argumentos var myObject = new MyType(args); auto myObject{ MyType{ args } }; o
auto myObject{ MyType(args) }; o
auto myObject = MyType{ args }; o
auto myObject = MyType(args); o
MyType myObject{ args }; o
MyType myObject(args);
Migración del método Footer_Click
Construcción de un objeto en una variable sin argumentos var myObject = new T(); MyType myObject; Migración del método BuildClipboardFormatsOutputString
Abreviatura de la inicialización del objeto var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Operación del vector masiva var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Migración del método CopyButton_Click
Iteración en la colección foreach (var v in c) for (auto&& v : c) Migración del método BuildClipboardFormatsOutputString
Detección de una excepción catch (Exception ex) catch (winrt::hresult_error const& ex) Migración del método PasteButton_Click
Detalles de la excepción ex.Message ex.message() Migración del método PasteButton_Click
Obtención de un valor de propiedad myObject.MyProperty myObject.MyProperty() Migración del método NotifyUser
Obtención de un valor de propiedad myObject.MyProperty = value; myObject.MyProperty(value);
Incremento de un valor de propiedad myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
Para las cadenas, cambie a un generador.
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Cadena de idioma para la cadena Windows Runtime N/A winrt::hstring{ s }
Creación de cadenas StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
String-building
Interpolación de cadenas $"{i++}) {s.Title}" winrt::to_hstring o winrt::hstring::operator+ Migración del método OnNavigatedTo
Cadena vacía para la comparación System.String.Empty winrt::hstring::empty Migración del método UpdateStatus
Creación de una cadena vacía var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
Operaciones de diccionario map[k] = v; // replaces any existing
v = map[k]; // throws if not present
map.ContainsKey(k)
map.Insert(k, v); // replaces any existing
v = map.Lookup(k); // throws if not present
map.HasKey(k)
Conversión de tipos (iniciar en caso de error) (MyType)v v.as<MyType>() Migración del método Footer_Click
Conversión de tipos (null en caso de error) v as MyType v.try_as<MyType>() Migración del método PasteButton_Click
Los elementos XAML con x:Name son propiedades. MyNamedElement MyNamedElement() Migración del constructor, Current y FEATURE_NAME
Cambio al subproceso de interfaz de usuario CoreDispatcher.RunAsync CoreDispatcher.RunAsync o winrt::resume_foreground Migración del método NotifyUser y Migración del método HistoryAndRoaming
Construcción de elementos de la interfaz de usuario con código imperativo en una página XAML Consulte la construcción de elementos de la interfaz de usuario. Consulte la construcción de elementos de la interfaz de usuario.

En las secciones siguientes se explican con más detalle los elementos de la tabla.

Construcción de elementos de la interfaz de usuario

En estos ejemplos de código se muestra la construcción de un elemento de la interfaz de usuario en el código imperativo de una página XAML.

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Windows::UI::Xaml::Style>(
        Resources().Lookup(
            winrt::box_value(L"MyTextBlockStyle")
        )
    )
);

ToString()

Los tipos de C# proporcionan el método Object.ToString.

int i = 2;
var s = i.ToString(); // s is a System.String with value "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# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
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.

Consulta también el tema Migración del método Footer_Click.

Creación de cadenas

En el caso de la creación de cadenas, C# tiene un tipo StringBuilder integrado.

Categoría C# C++/WinRT
Creación de cadenas StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Anexo de una cadena de Windows Runtime conservando los valores NULL builder.Append(s); builder << std::wstring_view{ s };
Adición de una nueva línea builder.Append(Environment.NewLine); builder << std::endl;
Acceso al resultado s = builder.ToString(); ws = builder.str();

Consulta también los temas Migración del método BuildClipboardFormatsOutputString y Migración del método DisplayChangedFormats.

Ejecución de código en el subproceso principal de la interfaz de usuario

Este ejemplo se toma del Ejemplo de escáner de códigos de barras.

Cuando quiera trabajar en el subproceso principal de la interfaz de usuario de un proyecto en C#, normalmente usará el método CoreDispatcher.RunAsync, de la siguiente manera.

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Do work on the main UI thread here.
    });
}

Es mucho más sencillo expresarlo en C++/WinRT. Observe que aceptamos parámetros por valor suponiendo que queremos acceder a ellos después del primer punto de suspensión (co_await, en este caso). Para más información, vea Paso de parámetros.

winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
    co_await Dispatcher();
    // Do work on the main UI thread here.
}

Si necesita hacer el trabajo con una prioridad distinta de la predeterminada, consulte la función winrt::resume_foreground, que tiene una sobrecarga que toma prioridad. Para ejemplos de código que muestran cómo esperar una llamada a winrt::resume_foreground, vea Programación teniendo en cuenta la afinidad de subprocesos.

Definición de las clases en tiempo de ejecución en IDL

Consulta los temas IDL para el tipo de MainPage y Consolidación de los archivos .idl.

Inclusión de los archivos de encabezado de espacio de nombres de Windows de C++/WinRT necesarios

En C++/WinRT, siempre que quieras usar un tipo desde un espacio de nombres de Windows, debes incluir el archivo de encabezado de espacio de nombres de Windows de C++/WinRT correspondiente. Para obtener un ejemplo, consulta el tema Migración del método NotifyUser.

Conversiones boxing y unboxing

C# 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# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
Operación C# 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# C++/WinRT
Conversión unboxing de un entero conocido i = (int)o; i = unbox_value<int>(o);
Si o es null System.NullReferenceException Bloqueo
Si o no es un entero con conversión boxing System.InvalidCastException Bloqueo
Unboxing de entero, usar reserva si es null; bloquear en cualquier otro caso i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Unboxing de entero, si es posible; usar reversión en cualquier otro caso i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Para obtener un ejemplo, consulta los temas Migración del método OnNavigatedTo y Migración del método Footer_Click.

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# 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# 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í.

Comportamiento C# 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 "" hstring{}
¿Son null y "" idénticos? No
Validez de null s = null;
s.Length genera NullReferenceException
s = hstring{};
s.size() == 0 (válido)
Si asignas una cadena null a un objeto o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
Si asignas "" a un objeto o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

Conversiones boxing y unboxing básicas.

Operación C# C++/WinRT
Aplicar boxing a una cadena o = s;
Una cadena vacía se convierte en un objeto con un valor distinto de null.
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 null.
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 = o as string;
Un objeto null o que no es una cadena se convierte en una cadena null.

O

s = o as string ?? fallback;
Un valor null o que no es una cadena se convierte en un elemento Fallback.
Una cadena vacía se conserva.
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.

Poner una clase a disposición de la extensión de marcado {Binding}

Si piensas usar la extensión de marcado {Binding} para enlazar datos al tipo de datos, consulta Objeto de enlace que se declara usando {Binding}.

Consumir objetos a partir del marcado XAML

En un proyecto de C#, 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#, 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.

Poner un origen de datos a disposición del marcado XAML

En C++/WinRT versión 2.0.190530.8 y posterior, winrt::single_threaded_observable_vector crea un vector observable que admite tanto IObservableVector<T> como IObservableVector<IInspectable>. Para obtener un ejemplo, consulta el tema Migración de la propiedad Scenarios.

Puedes crear el archivo Midl (.idl) de este modo (consulta también Factorizar clases en tiempo de ejecución en archivos (.idl).

namespace Bookstore
{
    runtimeclass BookSku { ... }

    runtimeclass BookstoreViewModel
    {
        Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
    }

    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

E implementarlo de este modo.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
	Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...

Para obtener más información, consulta los artículos Controles de elementos de XAML; enlazar a una colección C++/WinRT y Colecciones con C++/WinRT.

Poner un origen de datos a disposición del marcado XAML (antes de C++/WinRT 2.0.190530.8)

El enlace de datos XAML requiere que un origen de los elementos implemente IIterable<IInspectable>, así como una de las siguientes combinaciones de interfaces.

  • IObservableVector<IInspectable>
  • IBindableVector e INotifyCollectionChanged
  • IBindableVector e IBindableObservableVector
  • IBindableVector por sí mismo (no responderá a los cambios)
  • IVector<IInspectable>
  • IBindableIterable (se iterará y guardará los elementos en una colección privada)

No se puede detectar una interfaz genérica como IVector<T> en tiempo de ejecución. Cada IVector<T> tiene un identificador de interfaz (IID) diferente, que es una función de T. Cualquier desarrollador puede expandir el conjunto de T arbitrariamente, por lo que queda claro que el código de enlace XAML nunca puede conocer el conjunto completo al que va a consultar. Esa restricción no supone un problema para C#, ya que cada objeto CLR que implementa IEnumerable<T> implementa IEnumerable de manera automática. En el nivel de ABI, eso significa que cada objeto que implementa IObservableVector<T> implementa IObservableVector<IInspectable> de manera automática.

C++/WinRT no ofrece esa garantía. Si una clase de C++/WinRT en tiempo de ejecución implementa IObservableVector<T>, no podemos suponer que de, algún modo, también se proporciona una implementación de IObservableVector<IInspectable>.

Por lo tanto, así es como deberá verse el ejemplo anterior.

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

Y la implementación.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
    // This is really an observable vector of BookSku.
	Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...

Si tienes que acceder a objetos en m_bookSkus, tendrás que volver a aplicarles QueryInterface en Bookstore::BookSku.

Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
    for (auto&& obj : m_bookSkus)
    {
        auto bookSku = obj.as<Bookstore::BookSku>();
        if (bookSku.Title() == title) return bookSku;
    }
    return nullptr;
}

Clases derivadas

Para derivar a partir de una clase en tiempo de ejecución, la clase base debe admitir la composición. C# 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 el archivo de encabezado de tu tipo de 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>
    {
        ...
    }
}

API importantes