Migrar a C++/WinRT desde C#

Sugerencia

Si ya ha leído este artículo y vuelve a consultarlo con una tarea concreta en mente, puede ir directamente a la sección Encontrar contenido según la tarea que esté realizando de este artículo.

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

Para obtener un caso práctico de migración de uno de los ejemplos de aplicaciones de Plataforma universal de Windows (UWP), consulte el tema complementario Migración del ejemplo del Portapapeles a C++/WinRT desde C#. Puede adquirir práctica y experiencia en portabilidad siguiendo ese tutorial y migrando usted mismo el ejemplo sobre la marcha.

Preparación y qué esperar

El caso práctico Migración del ejemplo del Portapapeles a C++/WinRT de C# muestra ejemplos de los tipos de decisiones de diseño de software que tomarás al migrar un proyecto a C++/WinRT. Por lo tanto, es una buena idea prepararse para la portabilidad mediante una comprensión sólida de cómo funciona el código existente. De este modo, obtendrá una buena visión general de la funcionalidad de la aplicación y la estructura del código y, a continuación, las decisiones que tome siempre le llevarán adelante y en la dirección correcta.

En términos de qué tipos de cambios de portabilidad se esperan, puede agruparlos en cuatro categorías.

  • Portar la proyección de lenguaje. El 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. Para C#, algunos tipos de Windows Runtime se proyectan como tipos .NET. Así, por ejemplo, traducirá System.Collections.Generic.IReadOnlyList<T> de nuevo como Windows.Foundation.Collections.IVectorView<T>. Además, en C#, algunas operaciones de Windows Runtime se proyectan como características cómodas del lenguaje C#. Un ejemplo es que en C# se usa la sintaxis del += operador 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).
  • Sintaxis del lenguaje de puerto. Muchos de estos cambios son transformaciones mecánicas simples, reemplazando un símbolo por otro. Por ejemplo, se cambia un punto (.) por dos signos de dos puntos (::).
  • Procedimiento de lenguaje de puerto. Algunos de ellos pueden ser cambios simples y repetitivos (como de myObject.MyProperty a myObject.MyProperty()). Otros necesitan cambios más profundos (por ejemplo, migrar un procedimiento que implique el uso de System.Text.StringBuilder a uno que implique el uso de std::wostringstream).
  • Tareas relacionadas con la portabilidad específicas de C++/WinRT. C# se encarga implícitamente de ciertos detalles de Windows Runtime, entre bastidores. Estos detalles se realizan explícitamente en C++/WinRT. Un ejemplo es que se usa un .idl archivo para definir las clases en tiempo de ejecución.

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

Buscar contenido basado en la tarea que está realizando

tarea Contenido
Crear un componente de Windows Runtime (WRC) Se puede lograr cierta funcionalidad (o determinadas API llamadas) solo con C++. Puedes encapsular esa funcionalidad en un WRC de C++/WinRT y, a continuación, usar el WRC desde, por ejemplo, una aplicación en C#. Consulta componentes de Windows Runtime con C++/WinRT y Si vas a crear una clase de tiempo de ejecución en un componente de Windows Runtime.
Migrar un método asíncrono Es una buena idea que la primera línea de un método asíncrono de una clase del entorno de ejecución de C++/WinRT sea auto lifetime = get_strong(); (consulte Acceso seguro al puntero this en una corrutina miembro de una clase).

Para la portabilidad desde Task, consulte Acción asincrónica.
Migración desde Task<T>, consulte Operación asíncrona.
Para la portabilidad desde async void, consulte Método Fire-and-forget.
Migrar una clase En primer lugar, determine si la clase debe ser una clase en tiempo de ejecución o si puede ser una clase normal. Para ayudarle a decidirlo, consulte el comienzo de Author APIs with C++/WinRT. A continuación, vea las tres filas siguientes.
Portar una clase en tiempo de ejecución Clase que comparte la funcionalidad fuera de la aplicación de C++ o una clase que se usa en el enlace de datos XAML. Consulta 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 hace referencia en la interfaz de usuario XAML.

Estos vínculos describen esto con más detalle, pero una clase en tiempo de ejecución debe declararse en IDL. Si el project ya contiene un archivo IDL (por ejemplo, Project.idl), se recomienda declarar cualquier nueva clase en tiempo de ejecución en ese archivo. En 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 compilar y examine los archivos auxiliares (stub) generados (.h y .cpp) en la carpeta Generated Files de su proyecto (en Explorador de soluciones, con el nodo del proyecto seleccionado, asegúrese de que Mostrar todos los archivos esté activada). Compare los archivos de definición con los archivos que ya están en su proyecto, y añada archivos o añada/actualice firmas de funciones según sea necesario. La sintaxis del archivo Stub siempre es correcta, por lo que se recomienda usarla 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 hace referencia en la interfaz de usuario XAML
Uso de objetos en marcado XAML
Definición de las clases en tiempo de ejecución en IDL
Migrar una colección Colecciones con C++/WinRT
Hacer que un origen de datos esté disponible para el marcado XAML
Contenedor asociativo
Acceso a los miembros del vector
Portar un evento Delegado de controlador de eventos como miembro de una clase
Revocar delegado de controlador de eventos
Portar un método Desde C#: private async void SampleButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
Al archivo C++/WinRT .h: fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
Al archivo C++/WinRT .cpp: fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Cadenas de puertos Control de cadenas en C++/WinRT
ToString
Creación de cadenas
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. Vea también Boxing y 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 en la conversión.

Cambios que implican la proyección del lenguaje

Category C# C++/WinRT Consulte también
Objeto no tipado object, o System.Object Windows::Foundation::IInspectable Portabilidad 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() Portar el método BuildClipboardFormatsOutputString
Tipo de colección típico IList<T> y Agregar para agregar un elemento. IVector<T> y Append para agregar un elemento. Si usa un std::vector en cualquier lugar, use push_back para añadir un elemento.
Tipo de colección de solo lectura IReadOnlyList<T> IVectorView<T> Portar el método BuildClipboardFormatsOutputString
Delegado del controlador de eventos como miembro de clase myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Portabilidad del método EnableClipboardContentChangedNotifications
Revocar delegado de controlador de eventos myObject.EventName -= Handler; myObject.EventName(token); Portabilidad del método EnableClipboardContentChangedNotifications
Contenedor asociativo IDictionary<K, V> IMap<K, V>
Acceso a los miembros de un vector x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Registro o revocación de un controlador de eventos

En C++/WinRT, tiene varias opciones sintácticas para registrar o revocar un delegado de controlador de eventos, como se describe en Controlar eventos mediante delegados en C++/WinRT. Consulte también Adaptación del método EnableClipboardContentChangedNotifications.

A veces, por ejemplo, cuando un destinatario de eventos (un objeto que controla un evento) está a punto de destruirse, querrá revocar un controlador de eventos para que el origen del evento (el objeto que genera el evento) no llame a un objeto destruido. Consulte Revocar un delegado registrado. En casos como ese, cree una variable miembro event_token para los controladores de eventos. Para obtener un ejemplo, vea Portabilidad 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 todavía 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 registra un controlador de eventos solo 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::Microsoft::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::Microsoft::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}" />

Simplemente podrías cambiar ese marcado por la versión más simple: Click="OpenButton_Click". O bien, si lo prefiere, puede mantener ese marcado tal como está. Todo lo que hay que hacer para darle soporte es declarar el gestor de eventos en IDL.

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

Note

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

Cambios que implican la sintaxis del lenguaje

Category C# C++/WinRT Consulte también
Modificadores de acceso public \<member\> public:
    \<member\>
Portabilidad del método Button_Click
Acceso a un miembro de datos this.variable this->variable  
Acción asincrónica async Task ... IAsyncAction ... Interfaz IAsyncAction, simultaneidad y operaciones asincrónicas con C++/WinRT
Operación asincrónica async Task<T> ... IAsyncOperation<T> ... Interfaz IAsyncOperation, simultaneidad y operaciones asincrónicas con C++/WinRT
Método de envío y olvido (implica asincronía) async void ... winrt::fire_and_forget ... Portabilidad del método CopyButton_Click, Fire and forget
Acceso a una constante enumerada E.Value E::Value Portar el método DisplayChangedFormats
Espera cooperativa await ... co_await ... Portabilidad 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 espacio de nombres A.B.T A::B::T
Null null nullptr Portabilidad del método UpdateStatus
Obtener un objeto de tipo typeof(MyType) winrt::xaml_typename<MyType>() Portabilidad de la propiedad Scenarios
Declaración de parámetros para un método MyType MyType const& Paso de parámetros
Declaración de parámetros para un método asincrónico MyType MyType Paso de parámetros
Llamada a un método estático T.Method() T::Method()
Instrumentos de cuerda string, o System.String winrt::hstring Control de cadenas en C++/WinRT
Literal de cadena "a string literal" L"a string literal" Portabilidad del constructor, Current y FEATURE_NAME
Tipo inferido (o deducido) var auto Portar el método BuildClipboardFormatsOutputString
Using-directive using A.B.C; using namespace A::B::C; Potabilidad del constructor, Current y FEATURE_NAME
Literal de cadena sin formato/textual @"verbatim string literal" LR"(raw string literal)" Portabilidad del método DisplayToast

Note

Si un archivo de encabezado no contiene una using namespace directiva para un espacio de nombres determinado, tendrá que calificar completamente todos los nombres de tipo para ese espacio de nombres; o al menos calificarlos lo suficiente para que el compilador los encuentre. Para obtener un ejemplo, vea Portabilidad del método DisplayToast.

Migración de clases y miembros

Tendrá que decidir, para cada tipo de C#, si se debe portar a un tipo de Windows Runtime o a una clase/estructura/enumeración de C++ normal. Para obtener más información y ejemplos detallados que ilustran cómo tomar esas decisiones, consulte Migración del ejemplo del Portapapeles 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, conviértalos en miembros de datos de su tipo de implementación.

Un campo estático de C# se convierte en un descriptor de acceso estático de C++/WinRT o una función mutadora. Para obtener más información y un ejemplo, consulta Portabilidad 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 de cómo decidir, consulta IDL para el tipo MainPage.

Migración de archivos de marcado XAML y archivos de recursos

En el caso de migrar el ejemplo del Portapapeles 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 C++/WinRT. En algunos casos, las modificaciones en el marcado serán necesarias para lograrlo. Consulta el tema Copia de XAML y estilos necesarios para terminar de portar MainPage.

Cambios que implican procedimientos dentro del idioma

Category C# C++/WinRT Consulte también
Administración del ciclo de vida en un método async N/A auto lifetime{ get_strong() }; o
auto lifetime = get_strong();
Portabilidad del método CopyButton_Click
Cancelación using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Portabilidad del método CopyImage
Construir objeto new MyType(args) MyType{ args } o
MyType(args)
Portabilidad de la propiedad Scenarios
Crear referencia sin inicializar MyType myObject; MyType myObject{ nullptr }; o
MyType myObject = nullptr;
Portabilidad del constructor, Current y FEATURE_NAME
Construcción del objeto en 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);
Portabilidad del método Footer_Click
Construcción del objeto en variable sin argumentos var myObject = new T(); MyType myObject; Portar el método BuildClipboardFormatsOutputString
Sintaxis abreviada para la inicialización de objetos 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" });
Portabilidad del método CopyButton_Click
Iteración en la colección foreach (var v in c) for (auto&& v : c) Portar el método BuildClipboardFormatsOutputString
Detección de una excepción catch (Exception ex) catch (winrt::hresult_error const& ex) Portabilidad del método PasteButton_Click
Detalles de excepciones ex.Message ex.message() Portabilidad del método PasteButton_Click
Obtener el valor de una propiedad myObject.MyProperty myObject.MyProperty() Portabilidad del método NotifyUser
Establecer un valor de propiedad myObject.MyProperty = value; myObject.MyProperty(value);
Incrementar 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 }
Construcción de cadenas StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Creación de cadenas
Interpolación de cadenas $"{i++}) {s.Title}" winrt::to_hstring y/o winrt::hstring::operator+ Portabilidad del método OnNavigatedTo
Cadena vacía para la comparación System.String.Empty winrt::hstring::empty Portabilidad 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>() Portabilidad del método Footer_Click
Conversión de tipos (nulo en caso de fallo) v as MyType v.try_as<MyType>() Portabilidad del método PasteButton_Click
Los elementos XAML con x:Name son propiedades MyNamedElement MyNamedElement() Potabilidad del constructor, Current y FEATURE_NAME
Cambio al subproceso de interfaz de usuario CoreDispatcher.RunAsync DispatcherQueue.TryEnqueue, o winrt::resume_foreground Portar el método NotifyUser y portar el método HistoryAndRoaming
Construcción de elementos de interfaz de usuario en código imperativo en una página XAML Consulte construcción de elementos de interfaz de usuario. Consulte construcción de elementos de interfaz de usuario.

En las secciones siguientes se detallan más detalles sobre algunos de los elementos de la tabla.

Construcción de elementos de interfaz de usuario

Estos ejemplos de código muestran la construcción de un elemento de interfaz de usuario en el código imperativo de una página XAML.

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Microsoft.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Microsoft::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 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 los tipos adicionales a los que quieras aplicar stringify.

Language Convertir int en cadena Convertir enumeración en cadena
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>

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

Vea también la migración del método Footer_Click.

Construcción de cadenas

Para la creación de cadenas, C# tiene un tipo StringBuilder integrado.

Category C# C++/WinRT
Construcción de cadenas StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Anexar una cadena de Windows Runtime, preservando los caracteres nulos builder.Append(s); builder << std::wstring_view{ s };
Agregar una nueva línea builder.Append(Environment.NewLine); builder << std::endl;
Acceso al resultado s = builder.ToString(); ws = builder.str();

Consulte también 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ódigo de barras.

Cuando quieras trabajar en el subproceso principal de la interfaz de usuario de un proyecto de C#, normalmente usas el método DispatcherQueue.TryEnqueue (o el coreDispatcher.RunAsync anterior en UWP). Este es el aspecto del patrón en C#.

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    DispatcherQueue.TryEnqueue(() =>
    {
        // Do work on the main UI thread here.
    });
}

Es mucho más sencillo expresarlo en C++/WinRT. Observe que estamos aceptando parámetros por valor en la suposición de que queremos acceder a ellos después del primer punto de suspensión (, co_awaiten este caso). Para obtener más información, consulta Paso de parámetros.

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

Si necesitas realizar el trabajo con una prioridad distinta de la predeterminada, consulta la función winrt::resume_foreground , que tiene una sobrecarga que tiene una 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, cada vez que quieras usar un tipo de un espacio de nombres de Windows, debes incluir el archivo de encabezado del espacio de nombres de Windows correspondiente para C++/WinRT. Por ejemplo, consulte Adaptación del método NotifyUser.

Conversiones boxing y unboxing

C# convierte automáticamente los valores escalares en objetos. C++/WinRT requiere que llames a la función winrt::box_value explícitamente. Ambos lenguajes requieren que desempaquetes explícitamente. Consulta 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;
Operation C# C++/WinRT
Boxeo o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Desempaquetado i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

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

Scenario C# C++/WinRT
Desencapsular un entero conocido i = (int)o; i = unbox_value<int>(o);
Si o es null System.NullReferenceException Choque
Si o no es un int encapsulado System.InvalidCastException Choque
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 ver un ejemplo, consulte Adaptación del método OnNavigatedTo y Adaptación del método Footer_Click.

Conversiones boxing y unboxing en una cadena

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

El tipo ABI HSTRING es un puntero a una cadena con recuento de referencias. Pero no se deriva de IInspectable, por lo que no es técnicamente un objeto . Además, un HSTRING nulo representa la cadena vacía. 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 Windows Runtime como un tipo de referencia; mientras que C++/WinRT proyecta una cadena como un tipo de valor. Esto significa que una cadena nula encapsulada puede tener distintas representaciones según cómo se haya obtenido.

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

Conversiones boxing y unboxing básicas.

Operation C# C++/WinRT
Encapsular una cadena o = s;
La cadena vacía se convierte en un objeto no NULL.
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 nula.
InvalidCastException si no se trata de 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;
El objeto nulo o un valor que no es una cadena se convierte en la cadena «null».

OR

s = o as string ?? fallback;
Un valor null o que no es una cadena se convierte en un elemento Fallback.
Cadena vacía conservada.
s = unbox_value_or<hstring>(o, fallback);
Un valor null o que no es una cadena se convierte en un elemento Fallback.
Cadena vacía conservada.

Hacer que una clase esté disponible para 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 mediante la extensión de marcado XAML {x:Bind} deben exponerse públicamente en IDL.

Además, al enlazar con un Boolean se muestra true o false en C#, pero en C++/WinRT se muestra Windows.Foundation.IReference`1<Boolean>.

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

Hacer que un origen de datos esté disponible para el marcado XAML

En C++/WinRT versión 2.0.190530.8 o posterior, winrt::single_threaded_observable_vector crea un vector observable que admite IObservableVector<T> e IObservableVector<IInspectable>. Para obtener un ejemplo, vea Portabilidad de la propiedad Scenarios.

Puede crear su archivo Midl (.idl) de este modo (consulte también Separación de clases en tiempo de ejecución en archivos Midl (.idl)).

namespace Bookstore
{
    runtimeclass BookSku { ... }

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

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

E implemente así.

// 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 Controles de elementos XAML; enlazar a una colección de C++/WinRT y Colecciones con C++/WinRT.

Hacer que un origen de datos esté disponible para el marcado XAML (antes de C++/WinRT 2.0.190530.8)

El enlace de datos XAML requiere que una fuente de 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 (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 diferente (IID), que es una función de T. Cualquier desarrollador puede expandir el conjunto de T arbitrariamente, por lo que claramente el código de enlace XAML nunca puede conocer el conjunto completo para el que se va a consultar. Esa restricción no es un problema para C# porque cada objeto CLR que implementa IEnumerable T< implementa automáticamente IEnumerable>. En el nivel de ABI, esto significa que cada objeto que implementa IObservableVector<T> implementa automáticamente IObservableVector<IInspectable>.

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

Por consiguiente, 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 de una clase en tiempo de ejecución, la clase base debe ser composable. C# no requiere que realice ningún paso especial para que las clases se puedan componer, pero C++/WinRT sí. Usa la palabra clave unsealed para indicar que quieres que tu clase se pueda usar como clase base.

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

En el archivo de encabezado del tipo 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>
    {
        ...
    }
}

API importantes