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 resultar natural en el lenguaje de programación correspondiente. 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. Así que relacionará las características del lenguaje, como esa, con la operación fundamental que se está realizando (registro de eventos, en este ejemplo).
  • Sintaxis del lenguaje de puerto. Muchos de estos cambios son transformaciones mecánicas simples, reemplazando un símbolo por otro. Por ejemplo, cambiar punto (.) a 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).

Adaptación desde Task, consulte acción asíncrona.
Migración desde Task<T>, consulte Operación asíncrona.
Migración desde async void, vea 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.
Adaptar 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 stubs de su proyecto coincidan con los de los archivos de stubs, puede implementarlos trasladando el código en C#.
Portar una clase normal Consulte Si no está creando una clase en tiempo de ejecución.
IDL de autor 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
Adaptar 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
Boxing y unboxing de una cadena
Conversión de tipos (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)
Lanza una excepción si se produce un error de unboxing. Vea también Boxing y unboxing.

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
Devuelve el valor alternativo si falla el desempaquetado. Consulte también Boxing y unboxing.

C#: (Class)o
C++/WinRT: o.as<Class>()
Se produce si se produce un error en la 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 de controlador de eventos como miembro de una 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 que realiza el registro tenga acceso de amigo a tu tipo de implementación y que OpenButton_Click sea 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 va a migrar se enlaza al controlador de eventos desde el marcado (para obtener más información sobre ese escenario, vea Functions in 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

Declare la función como void incluso si la implemente como fire and forget.

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
Acceder 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 ... Adaptación del método CopyButton_Click, Enviar y olvidar
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; Portabilidad del constructor, Current y FEATURE_NAME
Literal de cadena textual/sin formato @"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#.

Normalmente, una propiedad de C# se convierte en un método de acceso, un método mutador y un campo de respaldo. Para obtener más información y un ejemplo, consulta Porting the IsClipboardContentChangedEnabled property.

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 miembro, de nuevo, tendrá que decidir en cada caso si debe formar parte de la IDL o si es una función miembro pública o privada de su 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 Copiar el XAML y los estilos necesarios para completar la migración de 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 de vector masivo 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 de la colección foreach (var v in c) for (auto&& v : c) Portar el método BuildClipboardFormatsOutputString
Detectar 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, use un constructor.
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Cadena de idioma a cadena de 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 (lanza un error si falla) (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() Portabilidad del constructor, Current y FEATURE_NAME
Cambiar al hilo de la 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 añadir sobrecargas para cualquier tipo adicional que quieras convertir en cadena.

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 el caso de convertir una enumeración en cadena, deberá proporcionar la implementación de winrt::to_hstring.

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

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

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

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

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 ver ejemplos de código que muestran cómo esperar una llamada a winrt::resume_foreground, consulte Programación con afinidad de subproceso en mente.

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

Consulte IDL para el tipo MainPage y Consolide sus .idl archivos.

Incluya los archivos de cabecera de los espacios de nombres de Windows de C++/WinRT que necesite

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.

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 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
Desempaquetar int, usar un valor alternativo si es null; fallar si es cualquier otra cosa i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Desencapsular int si es posible; usar una alternativa para 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.

Boxing y unboxing de una cadena

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

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

C# 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

Boxing y unboxing básicos.

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);
Se bloquea el objeto NULL.
Falla si no es una cadena.
Desencadene 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 nulo o que no sea una cadena se convierte en el valor predeterminado.
Cadena vacía conservada.
s = unbox_value_or<hstring>(o, fallback);
Un valor nulo o que no sea una cadena se convierte en el valor predeterminado.
Cadena vacía conservada.

Hacer que una clase esté disponible para la extensión de marcado {Binding}

Si tiene previsto usar la extensión de marcado {Binding} para establecer un enlace de datos con su tipo de datos, consulte objeto Binding declarado mediante {Binding}.

Consumo de objetos 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 obtener más información y ejemplos de código, consulte Consumo de objetos de 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 necesita acceder a los objetos de m_bookSkus, tendrá que hacerles QI de nuevo a 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