Componentes de Windows Runtime con C# y Visual Basic
Puedes usar código administrado para crear tus propios tipos de Windows Runtime y empaquetarlos en un componente de Windows Runtime. Puedes usar tu componente en aplicaciones de Plataforma universal de Windows (UWP) escritas en C++, JavaScript, Visual Basic o C#. En este tema se describen las reglas de creación de un componente y se analizan algunos aspectos de la compatibilidad con .NET para Windows Runtime. En general, esa compatibilidad está diseñada para ser transparente para los programadores de .NET. Sin embargo, cuando creas un componente para su uso con JavaScript o C++, debes tener en cuenta las diferencias en la forma en que esos lenguajes son compatibles con Windows Runtime.
Si vas a crear un componente para usarlo solo en aplicaciones para UWP escritas en Visual Basic o C#, y el componente no contiene controles para UWP, considera la posibilidad de usar la plantilla de proyecto Biblioteca de clases en lugar de la plantilla de proyecto Componente de Windows Runtime en Microsoft Visual Studio. Hay menos restricciones en una biblioteca de clases sencilla.
Nota:
Para los desarrolladores de C# que escriben aplicaciones de escritorio en .NET 6 o versiones posteriores, use C#/WinRT para crear un componente de Windows Runtime. Consulte Creación de los componentes de Windows Runtime con C#/WinRT.
Declarar tipos en componentes de Windows en tiempo de ejecución
Internamente, los tipos de Windows Runtime del componente pueden usar cualquier funcionalidad de .NET permitida en una aplicación para UWP. Para obtener más información, consulte las Aplicaciones para UWP de .NET.
Externamente, los miembros de los tipos solo pueden exponer los tipos de Windows Runtime para sus parámetros y valores devueltos. En la lista siguiente se describen las limitaciones de los tipos de .NET que se exponen desde un componente de Windows Runtime.
Los campos, parámetros y valores devueltos de todos los tipos y miembros públicos del componente deben ser tipos de Windows Runtime. Esta restricción incluye los tipos de Windows Runtime que creas, así como los tipos proporcionados por el propio Windows Runtime. También incluye una serie de tipos de .NET. La inclusión de estos tipos forma parte de la compatibilidad que proporciona .NET para habilitar el uso natural de Windows Runtime en código administrado; el código parece usar tipos de .NET conocidos en lugar de los tipos subyacentes de Windows Runtime. Por ejemplo, puede usar tipos primitivos de .NET como Int32 y Double, determinados tipos fundamentales, como DateTimeOffset y Uri, y algunos tipos de interfaz genéricos usados habitualmente, como IEnumerable T> (IEnumerable<(Of T) en Visual Basic) e IDictionary<TKey,TValue.> Tenga en cuenta que los argumentos de tipo de estos tipos genéricos deben ser tipos de Windows Runtime. Esto se describe en las secciones Pasar tipos de Windows Runtime al código administrado y Pasar tipos administrados a Windows Runtime, más adelante en este tema.
Las clases e interfaces públicas pueden contener métodos, propiedades y eventos. Puede declarar delegados para los eventos o usar el delegado EventHandler<T> . Una interfaz o clase pública no puede:
- Sea genérico.
- Implemente una interfaz que no sea una interfaz de Windows Runtime (sin embargo, puede crear sus propias interfaces de Windows Runtime e implementarlas).
- Derive de tipos que no están en Windows Runtime, como System.Exception y System.EventArgs.
Todos los tipos públicos deben tener un espacio de nombres raíz que coincida con el nombre del ensamblado y el nombre del ensamblado no debe comenzar con "Windows".
Sugerencia. De forma predeterminada, los proyectos de Visual Studio tienen nombres de espacio de nombres que coinciden con el nombre del ensamblado. En Visual Basic, la instrucción Namespace para este espacio de nombres predeterminado no se muestra en el código.
Las estructuras públicas no pueden tener ningún miembro distinto de los campos públicos y esos campos deben ser tipos de valor o cadenas.
Las clases públicas deben estar selladas (NotInheritable en Visual Basic). Si el modelo de programación requiere polimorfismo, puede crear una interfaz pública e implementar esa interfaz en las clases que deben ser polimórficas.
Depuración del componente
Si la aplicación para UWP y el componente se compilan con código administrado, puedes depurarlos al mismo tiempo.
Al probar el componente como parte de una aplicación para UWP con C++, puedes depurar código administrado y nativo al mismo tiempo. El valor predeterminado es solo código nativo.
Para depurar código nativo de C++ y código administrado
- Abra el menú contextual del proyecto de Visual C++ y elija Propiedades.
- En las páginas de propiedades, en Propiedades de configuración, elija Depuración.
- Elija Tipo de depurador y, en el cuadro de lista desplegable, cambie Solo nativo a Mixto (administrado y nativo). Elija Aceptar.
- Establezca puntos de interrupción en código nativo y administrado.
Al probar el componente como parte de una aplicación para UWP mediante JavaScript, de forma predeterminada la solución está en modo de depuración de JavaScript. En Visual Studio, no se puede depurar JavaScript ni código administrado al mismo tiempo.
Para depurar código administrado en lugar de JavaScript
- Abra el menú contextual del proyecto de JavaScript y elija Propiedades.
- En las páginas de propiedades, en Propiedades de configuración, elija Depuración.
- Elija Tipo de depurador y, en el cuadro de lista desplegable, cambie Script Only (Solo script) a Managed Only (Solo administrado). Elija Aceptar.
- Establezca puntos de interrupción en código administrado y depure como de costumbre.
Pasar tipos de Windows Runtime al código administrado
Como se mencionó anteriormente en la sección Declaración de tipos en componentes de Windows Runtime, determinados tipos de .NET pueden aparecer en las firmas de miembros de clases públicas. Esto forma parte de la compatibilidad que proporciona .NET para habilitar el uso natural de Windows Runtime en código administrado. Incluye tipos primitivos y algunas clases e interfaces. Cuando el componente se usa desde JavaScript o desde código de C++, es importante saber cómo aparecen los tipos de .NET al autor de la llamada. Consulte Tutorial sobre cómo crear un componente de Windows Runtime de C# o Visual Basic y llamarlo desde JavaScript para obtener ejemplos con JavaScript. En esta sección se describen los tipos usados habitualmente.
En .NET, los tipos primitivos, como la estructura Int32 , tienen muchas propiedades y métodos útiles, como el método TryParse . Por el contrario, los tipos primitivos y las estructuras de Windows Runtime solo tienen campos. Al pasar estos tipos al código administrado, parecen ser tipos de .NET y puede usar las propiedades y los métodos de tipos de .NET como lo haría normalmente. En la lista siguiente se resumen las sustituciones que se realizan automáticamente en el IDE:
- Para los primitivos de Windows Runtime Int32, Int64, Single, Double, Boolean, String (una colección inmutable de caracteres Unicode), Enum, UInt32, UInt64 y Guid, use el tipo del mismo nombre en el espacio de nombres Del sistema.
- Para UInt8, use System.Byte.
- Para Char16, use System.Char.
- Para la interfaz IInspectable , use System.Object.
Si C# o Visual Basic proporciona una palabra clave de lenguaje para cualquiera de estos tipos, puede usar la palabra clave language en su lugar.
Además de los tipos primitivos, algunos tipos básicos y usados habitualmente de Windows Runtime aparecen en código administrado como sus equivalentes de .NET. Por ejemplo, supongamos que el código de JavaScript usa la clase Windows.Foundation.Uri y quiere pasarlo a un método de C# o Visual Basic. El tipo equivalente en código administrado es la clase System.Uri de .NET y ese es el tipo que se va a usar para el parámetro de método. Puedes saber cuándo aparece un tipo de Windows Runtime como un tipo de .NET, ya que IntelliSense en Visual Studio oculta el tipo de Windows Runtime al escribir código administrado y presenta el tipo de .NET equivalente. (Normalmente, los dos tipos tienen el mismo nombre. Sin embargo, tenga en cuenta que la estructura Windows.Foundation.DateTime aparece en código administrado como System.DateTimeOffset y no como System.DateTime).
Para algunos tipos de colección usados habitualmente, la asignación es entre las interfaces implementadas por un tipo de Windows Runtime y las interfaces implementadas por el tipo de .NET correspondiente. Al igual que con los tipos mencionados anteriormente, se declaran los tipos de parámetro mediante el tipo de .NET. Esto oculta algunas diferencias entre los tipos y hace que escribir código .NET sea más natural.
En la tabla siguiente se enumeran los tipos de interfaz genéricos más comunes, junto con otras asignaciones de interfaz y clases comunes. Para obtener una lista completa de los tipos de Windows Runtime que asigna .NET, vea Asignaciones de .NET de tipos de Windows Runtime.
Windows en tiempo de ejecución | .NET |
---|---|
IIterable<T> | IEnumerable<T> |
IVector<T> | IList<T> |
IVectorView<T> | IReadOnlyList<T> |
IMap<K, V> | IDictionary<TKey, TValue> |
IMapView<K, V> | IReadOnlyDictionary<TKey, TValue> |
IKeyValuePair<K, V> | KeyValuePair<TKey, TValue> |
IBindableIterable | IEnumerable |
IBindableVector | IList |
Windows.UI.Xaml.Data.INotifyPropertyChanged | System.ComponentModel.INotifyPropertyChanged |
Windows.UI.Xaml.Data.PropertyChangedEventHandler | System.ComponentModel.PropertyChangedEventHandler |
Windows.UI.Xaml.Data.PropertyChangedEventArgs | System.ComponentModel.PropertyChangedEventArgs |
Cuando un tipo implementa más de una interfaz, puede usar cualquiera de las interfaces que implementa como un tipo de parámetro o un tipo de valor devuelto de un miembro. Por ejemplo, puede pasar o devolver un objeto Dictionary<int, string> (Dictionary(Of Integer, String) en Visual Basic) como IDictionary<int, string>, IReadOnlyDictionary<int, string> o IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>>.
Importante
JavaScript usa la interfaz que aparece primero en la lista de interfaces que implementa un tipo administrado. Por ejemplo, si devuelve Dictionary<int, string> a código JavaScript, aparece como IDictionary<int, cadena> independientemente de la interfaz que especifique como tipo de valor devuelto. Esto significa que si la primera interfaz no incluye un miembro que aparece en interfaces posteriores, ese miembro no es visible para JavaScript.
En Windows Runtime, IMap<K, V> e IMapView<K, V> se iteran mediante IKeyValuePair. Al pasarlos al código administrado, aparecen como IDictionary<TKey, TValue> e IReadOnlyDictionary<TKey, TValue>, por lo que, naturalmente, se usa System.Collections.Generic.KeyValuePair<TKey, TValue> para enumerarlos.
La forma en que las interfaces aparecen en código administrado afecta a la forma en que aparecen los tipos que implementan estas interfaces. Por ejemplo, la clase PropertySet implementa IMap<K, V>, que aparece en código administrado como IDictionary<TKey, TValue>. PropertySet aparece como si implementara IDictionary<TKey, TValue> en lugar de IMap<K, V>, por lo que en el código administrado parece tener un método Add , que se comporta como el método Add en los diccionarios de .NET. No parece tener un método Insert . Puede ver este ejemplo en el tema Tutorial sobre cómo crear un componente de Windows Runtime de C# o Visual Basic y llamarlo desde JavaScript.
Pasar tipos administrados a Windows Runtime
Como se explicó en la sección anterior, algunos tipos de Windows Runtime pueden aparecer como tipos de .NET en las firmas de los miembros del componente o en las firmas de los miembros de Windows Runtime cuando los usas en el IDE. Al pasar tipos de .NET a estos miembros o usarlos como valores devueltos de los miembros del componente, aparecen en el código del otro lado como el tipo de Windows Runtime correspondiente. Para obtener ejemplos de los efectos que puede tener cuando se llama al componente desde JavaScript, vea la sección "Devolver tipos administrados del componente" en Tutorial sobre cómo crear un componente de Windows Runtime de C# o Visual Basic y llamarlo desde JavaScript.
Métodos sobrecargados
En Windows Runtime, los métodos se pueden sobrecargar. Sin embargo, si declaras varias sobrecargas con el mismo número de parámetros, debes aplicar el atributo Windows.Foundation.Metadata.DefaultOverloadAttribute a solo una de esas sobrecargas. Esa sobrecarga es la única a la que se puede llamar desde JavaScript. Por ejemplo, en el código siguiente, la sobrecarga que toma un valor int (Integer en Visual Basic) es la sobrecarga predeterminada.
public string OverloadExample(string s)
{
return s;
}
[Windows.Foundation.Metadata.DefaultOverload()]
public int OverloadExample(int x)
{
return x;
}
Public Function OverloadExample(ByVal s As String) As String
Return s
End Function
<Windows.Foundation.Metadata.DefaultOverload> _
Public Function OverloadExample(ByVal x As Integer) As Integer
Return x
End Function
[IMPORTANTE] JavaScript permite pasar cualquier valor a OverloadExample y coerce el valor al tipo requerido por el parámetro . Puede llamar a OverloadExample con "cuarenta y dos", "42" o 42.3, pero todos esos valores se pasan a la sobrecarga predeterminada. La sobrecarga predeterminada del ejemplo anterior devuelve 0, 42 y 42, respectivamente.
No se puede aplicar el atributo DefaultOverloadAttribute a constructores. Todos los constructores de una clase deben tener distintos números de parámetros.
Implementación de IStringable
A partir de Windows 8.1, Windows Runtime incluye una interfaz IStringable cuyo único método, IStringable.ToString, proporciona compatibilidad básica de formato comparable a la proporcionada por Object.ToString. Si decide implementar IStringable en un tipo administrado público que se exporta en un componente de Windows Runtime, se aplican las restricciones siguientes:
Puede definir la interfaz IStringable solo en una relación "class implements", como el código siguiente en C#:
public class NewClass : IStringable
O el siguiente código de Visual Basic:
Public Class NewClass : Implements IStringable
No se puede implementar IStringable en una interfaz.
No se puede declarar un parámetro para que sea de tipo IStringable.
IStringable no puede ser el tipo de valor devuelto de un método, una propiedad o un campo.
No se puede ocultar la implementación de IStringable de las clases base mediante una definición de método como la siguiente:
public class NewClass : IStringable { public new string ToString() { return "New ToString in NewClass"; } }
En su lugar, la implementación de IStringable.ToString siempre debe invalidar la implementación de la clase base. Solo puede ocultar una implementación de ToString invocando en una instancia de clase fuertemente tipada.
Nota:
En una variedad de condiciones, las llamadas desde código nativo a un tipo administrado que implementa IStringable u oculta su implementación de ToString pueden producir un comportamiento inesperado.
Operaciones asincrónicas
Para implementar un método asincrónico en el componente, agregue "Async" al final del nombre del método y devuelva una de las interfaces de Windows Runtime que representan acciones o operaciones asincrónicas: IAsyncAction, IAsyncActionWithProgress TProgress><, IAsyncOperation<TResult> o IAsyncOperationWithProgress<TResult, TProgress>.
Puede usar tareas de .NET (la clase Task y la clase TResult> de tarea<genérica) para implementar el método asincrónico. Debe devolver una tarea que represente una operación en curso, como una tarea que se devuelve de un método asincrónico escrito en C# o Visual Basic, o una tarea que se devuelve del método Task.Run. Si usa un constructor para crear la tarea, debe llamar a su método Task.Start antes de devolverla.
Un método que usa await
(Await
en Visual Basic) requiere la async
palabra clave (Async
en Visual Basic). Si expones este método desde un componente de Windows Runtime, aplica la async
palabra clave al delegado que pasas al método Run .
Para las acciones y operaciones asincrónicas que no admiten la cancelación o los informes de progreso, puede usar el método de extensión WindowsRuntimeSystemExtensions.AsAsyncAction o AsAsyncOperation<TResult> para encapsular la tarea en la interfaz adecuada. Por ejemplo, el código siguiente implementa un método asincrónico mediante el método TResult> Task.Run<para iniciar una tarea. El método de extensión AsAsyncOperation<TResult> devuelve la tarea como una operación asincrónica de Windows Runtime.
public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
return Task.Run<IList<string>>(async () =>
{
var data = await DownloadDataAsync(id);
return ExtractStrings(data);
}).AsAsyncOperation();
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
As IAsyncOperation(Of IList(Of String))
Return Task.Run(Of IList(Of String))(
Async Function()
Dim data = Await DownloadDataAsync(id)
Return ExtractStrings(data)
End Function).AsAsyncOperation()
End Function
El código JavaScript siguiente muestra cómo se podría llamar al método mediante un objeto WinJS.Promise. La función que se pasa al método then se ejecuta cuando se completa la llamada asincrónica. El parámetro stringList contiene la lista de cadenas devueltas por el método DownloadAsStringAsync y la función realiza cualquier procesamiento necesario.
function asyncExample(id) {
var result = SampleComponent.Example.downloadAsStringAsync(id).then(
function (stringList) {
// Place code that uses the returned list of strings here.
});
}
Para las acciones y operaciones asincrónicas que admiten la cancelación o los informes de progreso, usa la clase AsyncInfo para generar una tarea iniciada y enlazar las características de informes de cancelación y progreso de la tarea con las características de informes de cancelación y progreso de la interfaz de Windows Runtime adecuada. Para obtener un ejemplo que admita informes de cancelación y progreso, vea Tutorial sobre cómo crear un componente de Windows Runtime de C# o Visual Basic y llamarlo desde JavaScript.
Tenga en cuenta que puede usar los métodos de la clase AsyncInfo incluso si el método asincrónico no admite la cancelación ni los informes de progreso. Si usa una función lambda de Visual Basic o un método anónimo de C#, no proporcione parámetros para la interfaz de token e IProgress<T> . Si usa una función lambda de C#, proporcione un parámetro de token pero omisión. El ejemplo anterior, que usó el método AsAsyncOperation TResult, tiene este aspecto cuando se usa la sobrecarga del método AsyncInfo.Run<TResult>(Func<CancellationToken, Task<TResult>>).><
public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
return AsyncInfo.Run<IList<string>>(async (token) =>
{
var data = await DownloadDataAsync(id);
return ExtractStrings(data);
});
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
As IAsyncOperation(Of IList(Of String))
Return AsyncInfo.Run(Of IList(Of String))(
Async Function()
Dim data = Await DownloadDataAsync(id)
Return ExtractStrings(data)
End Function)
End Function
Si crea un método asincrónico que admite opcionalmente informes de cancelación o progreso, considere la posibilidad de agregar sobrecargas que no tengan parámetros para un token de cancelación o la interfaz de IProgress<T> .
Iniciar excepciones
Puede iniciar cualquier tipo de excepción que se incluya en .NET para aplicaciones de Windows. No puedes declarar tus propios tipos de excepción públicos en un componente de Windows Runtime, pero puedes declarar y producir tipos no públicos.
Si el componente no controla la excepción, se genera una excepción correspondiente en el código que llamó al componente. La forma en que aparece la excepción al autor de la llamada depende de la forma en que el lenguaje de llamada admita Windows Runtime.
En JavaScript, la excepción aparece como un objeto en el que el mensaje de excepción se reemplaza por un seguimiento de pila. Al depurar la aplicación en Visual Studio, puede ver el texto del mensaje original que se muestra en el cuadro de diálogo de excepción del depurador, identificado como "Información de WinRT". No se puede acceder al texto del mensaje original desde código JavaScript.
Sugerencia. Actualmente, el seguimiento de la pila contiene el tipo de excepción administrada, pero no se recomienda analizar el seguimiento para identificar el tipo de excepción. En su lugar, use un valor HRESULT como se describe más adelante en esta sección.
En C++, la excepción aparece como una excepción de plataforma. Si la propiedad HResult de la excepción administrada se puede asignar a HRESULT de una excepción de plataforma específica, se usa la excepción específica; De lo contrario, se produce una excepción Platform::COMException. El texto del mensaje de la excepción administrada no está disponible para el código de C++. Si se produjo una excepción de plataforma específica, aparece el texto del mensaje predeterminado para ese tipo de excepción; de lo contrario, no aparece ningún texto de mensaje. Consulte Excepciones (C++/CX).
En C# o Visual Basic, la excepción es una excepción administrada normal.
Al iniciar una excepción del componente, puede facilitar que un autor de llamada de JavaScript o C++ controle la excepción iniciando un tipo de excepción no público cuyo valor de propiedad HResult sea específico del componente. HRESULT está disponible para un llamador de JavaScript a través de la propiedad number del objeto de excepción y para un llamador de C++ a través de la propiedad COMException::HResult.
Nota:
Use un valor negativo para HRESULT. Un valor positivo se interpreta como correcto y no se produce ninguna excepción en el llamador de JavaScript o C++.
Declarar y generar eventos
Al declarar un tipo para contener los datos del evento, derive de Object en lugar de de EventArgs, ya que EventArgs no es un tipo de Windows Runtime. Use EventHandler<TEventArgs> como el tipo del evento y use el tipo de argumento de evento como argumento de tipo genérico. Genere el evento igual que lo haría en una aplicación .NET.
Cuando se usa el componente de Windows Runtime desde JavaScript o C++, el evento sigue el patrón de eventos de Windows Runtime que esperan esos lenguajes. Cuando se usa el componente de C# o Visual Basic, el evento aparece como un evento de .NET normal. Se proporciona un ejemplo en Tutorial para crear un componente de Windows Runtime de C# o Visual Basic y llamarlo desde JavaScript.
Si implementas descriptores de acceso de eventos personalizados (declara un evento con la palabra clave Custom , en Visual Basic), debes seguir el patrón de eventos de Windows Runtime en la implementación. Consulta Eventos personalizados y descriptores de acceso de eventos en componentes de Windows Runtime. Tenga en cuenta que, al controlar el evento desde código de C# o Visual Basic, parece ser un evento de .NET normal.
Pasos siguientes
Después de crear un componente de Windows Runtime para su propio uso, es posible que la funcionalidad que encapsula sea útil para otros desarrolladores. Tiene dos opciones para empaquetar un componente para su distribución a otros desarrolladores. Consulta Distribuir un componente administrado de Windows Runtime.
Para obtener más información sobre las características del lenguaje Visual Basic y C#, y la compatibilidad de .NET con Windows Runtime, consulte la documentación de Visual Basic y C# .
Solución de problemas
Síntoma | Solución |
---|---|
En una aplicación de C++ o WinRT, al consumir un componente de Windows Runtime para C# que usa XAML, el compilador genera un error con el formato "'MyNamespace_XamlTypeInfo': no es miembro de 'winrt::MyNamespace'" donde MyNamespace es el nombre del espacio de nombres del componente de Windows Runtime. | En el archivo pch.h de la aplicación que consume C++ o WinRT, agregue #include <winrt/MyNamespace.MyNamespace_XamlTypeInfo.h> y reemplace MyNamespace según corresponda. |