Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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.MyPropertyamyObject.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
.idlarchivo 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)oC++/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? ?? fallbackC++/WinRT: unbox_value_or<Value>(o, fallback)Devuelve el valor alternativo si falla el desempaquetado. Consulte también Boxing y unboxing. C#: (Class)oC++/WinRT: o.as<Class>()Se produce si se produce un error en la conversión. C#: o as ClassC++/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() }; oauto 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 } oMyType(args) |
Portabilidad de la propiedad Scenarios |
| Crear referencia sin inicializar | MyType myObject; |
MyType myObject{ nullptr }; oMyType 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 existingv = map[k]; // throws if not presentmap.ContainsKey(k) |
map.Insert(k, v); // replaces any existingv = map.Lookup(k); // throws if not presentmap.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.
Tareas de migración específicas de C++/WinRT
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>
{
...
}
}