Composants Windows Runtime avec C# et Visual Basic
Vous pouvez utiliser du code managé pour créer vos propres types Windows Runtime et les empaqueter dans un composant Windows Runtime. Vous pouvez utiliser votre composant dans des applications plateforme Windows universelle (UWP) écrites en C++, JavaScript, Visual Basic ou C#. Cette rubrique présente les règles de création d’un composant et décrit certains aspects de la prise en charge de .NET pour Windows Runtime. En règle générale, cette prise en charge est conçue pour être transparente pour les programmeurs .NET. Toutefois, lorsque vous créez un composant à utiliser avec JavaScript ou C++, vous devez tenir compte des différences de prise en charge de Windows Runtime par ces langages.
Si vous créez un composant à utiliser uniquement dans les applications UWP écrites en Visual Basic ou C#, et que le composant ne contient pas de contrôles UWP, envisagez d’utiliser le modèle bibliothèque de classes au lieu du modèle de projet composant Windows Runtime dans Microsoft Visual Studio. Il existe moins de restrictions sur une bibliothèque de classes simple.
Remarque
Pour les développeurs C# écrivant des applications de bureau dans .NET 6 ou ultérieur, utilisez C#/WinRT pour créer un composant Windows Runtime. Consultez Créer des composants Windows Runtime avec C#/WinRT.
Déclaration de types dans les composants Windows Runtime
En interne, les types Windows Runtime dans votre composant peuvent utiliser toutes les fonctionnalités .NET autorisées dans une application UWP. Pour plus d’informations, consultez .NET pour les applications UWP.
En externe, les membres de vos types peuvent exposer uniquement les types Windows Runtime pour leurs paramètres et leurs valeurs de retour. La liste suivante décrit les limitations relatives aux types .NET exposés à partir d’un composant Windows Runtime.
Les champs, paramètres et valeurs de retour de tous les types et membres publics de votre composant doivent être des types Windows Runtime. Cette restriction inclut les types Windows Runtime que vous créez ainsi que les types fournis par Windows Runtime lui-même. Il inclut également un certain nombre de types .NET. L’inclusion de ces types fait partie de la prise en charge que .NET fournit pour permettre l’utilisation naturelle de Windows Runtime dans le code managé. Votre code semble utiliser des types .NET familiers au lieu des types Windows Runtime sous-jacents. Par exemple, vous pouvez utiliser des types primitifs .NET tels que Int32 et Double, certains types fondamentaux tels que DateTimeOffset et Uri, et certains types d’interface génériques couramment utilisés tels que IEnumerable T> (IEnumerable<(Of T) en Visual Basic) et IDictionary<TKey,TValue>. Notez que les arguments de type de ces types génériques doivent être des types Windows Runtime. Ceci est abordé dans les sections Passage de types Windows Runtime au code managé et passage de types managés à Windows Runtime, plus loin dans cette rubrique.
Les classes et interfaces publiques peuvent contenir des méthodes, des propriétés et des événements. Vous pouvez déclarer des délégués pour vos événements ou utiliser le délégué T> EventHandler<. Une classe publique ou une interface ne peut pas :
- Soyez générique.
- Implémentez une interface qui n’est pas une interface Windows Runtime (toutefois, vous pouvez créer vos propres interfaces Windows Runtime et les implémenter).
- Dérivez de types qui ne se trouvent pas dans Windows Runtime, tels que System.Exception et System.EventArgs.
Tous les types publics doivent avoir un espace de noms racine qui correspond au nom de l’assembly, et le nom de l’assembly ne doit pas commencer par « Windows ».
Conseil. Par défaut, les projets Visual Studio ont des noms d’espace de noms qui correspondent au nom de l’assembly. Dans Visual Basic, l’instruction Namespace pour cet espace de noms par défaut n’est pas affichée dans votre code.
Les structures publiques ne peuvent pas avoir de membres autres que les champs publics, et ces champs doivent être des types de valeur ou des chaînes.
Les classes publiques doivent être scellées (NotInheritable en Visual Basic). Si votre modèle de programmation nécessite un polymorphisme, vous pouvez créer une interface publique et implémenter cette interface sur les classes qui doivent être polymorphes.
Débogage de votre composant
Si votre application UWP et votre composant sont générés avec du code managé, vous pouvez les déboguer en même temps.
Lorsque vous testez votre composant dans le cadre d’une application UWP à l’aide de C++, vous pouvez déboguer du code managé et natif en même temps. La valeur par défaut est du code natif uniquement.
Pour déboguer à la fois du code C++ natif et du code managé
- Ouvrez le menu contextuel de votre projet Visual C++, puis choisissez Propriétés.
- Dans les pages de propriétés, sous Propriétés de configuration, choisissez Débogage.
- Choisissez Type de débogueur et, dans la zone de liste déroulante, remplacez Native Uniquement par Mixte (managé et natif). Choisissez OK.
- Définissez des points d’arrêt dans du code natif et managé.
Lorsque vous testez votre composant dans le cadre d’une application UWP à l’aide de JavaScript, par défaut, la solution est en mode débogage JavaScript. Dans Visual Studio, vous ne pouvez pas déboguer javaScript et code managé en même temps.
Pour déboguer du code managé au lieu de JavaScript
- Ouvrez le menu contextuel de votre projet JavaScript, puis choisissez Propriétés.
- Dans les pages de propriétés, sous Propriétés de configuration, choisissez Débogage.
- Choisissez Type de débogueur et, dans la zone de liste déroulante, remplacez Le script uniquement par Managed Only. Choisissez OK.
- Définissez les points d’arrêt dans le code managé et déboguez comme d’habitude.
Passage de types Windows Runtime à du code managé
Comme mentionné précédemment dans la section Déclaration de types dans les composants Windows Runtime, certains types .NET peuvent apparaître dans les signatures des membres de classes publiques. Cela fait partie de la prise en charge que .NET fournit pour permettre l’utilisation naturelle de Windows Runtime dans le code managé. Il inclut des types primitifs et certaines classes et interfaces. Lorsque votre composant est utilisé à partir de JavaScript ou à partir du code C++, il est important de savoir comment vos types .NET apparaissent à l’appelant. Consultez la procédure pas à pas de création d’un composant C# ou Visual Basic Windows Runtime et appelez-le à partir de JavaScript pour obtenir des exemples avec JavaScript. Cette section traite des types couramment utilisés.
Dans .NET, les types primitifs tels que la structure Int32 ont de nombreuses propriétés et méthodes utiles, telles que la méthode TryParse . En revanche, les types primitifs et les structures dans Windows Runtime ont uniquement des champs. Lorsque vous passez ces types à du code managé, ils semblent être des types .NET, et vous pouvez utiliser les propriétés et méthodes des types .NET comme vous le feriez normalement. La liste suivante récapitule les substitutions effectuées automatiquement dans l’IDE :
- Pour les primitives Windows Runtime Int32, Int64, Single, Double, Boolean, String (collection immuable de caractères Unicode), Enum, UInt32, UInt64 et Guid, utilisez le type du même nom dans l’espace de noms System.
- Pour UInt8, utilisez System.Byte.
- Pour Char16, utilisez System.Char.
- Pour l’interface IInspectable , utilisez System.Object.
Si C# ou Visual Basic fournit un mot clé de langage pour l’un de ces types, vous pouvez utiliser le mot clé de langage à la place.
En plus des types primitifs, certains types Windows Runtime couramment utilisés apparaissent dans le code managé en tant qu’équivalents .NET. Par exemple, supposons que votre code JavaScript utilise la classe Windows.Foundation.Uri et que vous souhaitez la transmettre à une méthode C# ou Visual Basic. Le type équivalent dans le code managé est la classe .NET System.Uri et c’est le type à utiliser pour le paramètre de méthode. Vous pouvez indiquer quand un type Windows Runtime apparaît en tant que type .NET, car IntelliSense dans Visual Studio masque le type Windows Runtime lorsque vous écrivez du code managé et présente le type .NET équivalent. (Généralement, les deux types ont le même nom. Toutefois, notez que la structure Windows.Foundation.DateTime apparaît dans le code managé en tant que System.DateTimeOffset et non en tant que System.DateTime.)
Pour certains types de collection couramment utilisés, le mappage est entre les interfaces implémentées par un type Windows Runtime et les interfaces implémentées par le type .NET correspondant. Comme pour les types mentionnés ci-dessus, vous déclarez des types de paramètres à l’aide du type .NET. Cela masque certaines différences entre les types et rend l’écriture de code .NET plus naturelle.
Le tableau suivant répertorie les types d’interface génériques les plus courants, ainsi que d’autres mappages communs de classes et d’interface. Pour obtenir la liste complète des types Windows Runtime mappés par .NET, consultez les mappages .NET des types Windows Runtime.
Windows Runtime | .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 |
Lorsqu’un type implémente plusieurs interfaces, vous pouvez utiliser l’une des interfaces qu’il implémente en tant que type de paramètre ou type de retour d’un membre. Par exemple, vous pouvez passer ou retourner un dictionnaire int, une chaîne> (Dictionary<(Of Integer, String) en Visual Basic) en tant qu’int IDictionary int, string>, IReadOnlyDictionary<<int, string> ou IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue.>>
Important
JavaScript utilise l’interface qui apparaît en premier dans la liste des interfaces qu’un type managé implémente. Par exemple, si vous retournez dictionnaire<int, chaîne> au code JavaScript, il apparaît en tant qu’int IDictionary<, chaîne> quelle que soit l’interface que vous spécifiez comme type de retour. Cela signifie que si la première interface n’inclut pas de membre qui apparaît sur les interfaces ultérieures, ce membre n’est pas visible par JavaScript.
Dans Windows Runtime, IMap<K, V> et IMapView<K, V> sont itérés à l’aide de IKeyValuePair. Lorsque vous les transmettez au code managé, ils apparaissent sous la forme IDictionary<TKey, TValue> et IReadOnlyDictionary<TKey, TValue>. Par conséquent, vous utilisez naturellement System.Collections.Generic.KeyValuePair<TKey, TValue> pour les énumérer.
La façon dont les interfaces apparaissent dans le code managé affecte la façon dont les types qui implémentent ces interfaces apparaissent. Par exemple, la classe PropertySet implémente IMap<K, V>, qui apparaît dans le code managé en tant que TKey IDictionary<, TValue>. PropertySet apparaît comme s’il implémentait IDictionary<TKey, TValue> au lieu d’IMap< K, V>. Par conséquent, dans le code managé, il semble avoir une méthode Add, qui se comporte comme la méthode Add sur les dictionnaires .NET. Il n’apparaît pas avoir de méthode Insert . Vous pouvez voir cet exemple dans la rubrique Procédure pas à pas de création d’un composant Windows Runtime C# ou Visual Basic, et l’appeler à partir de JavaScript.
Passage de types managés à Windows Runtime
Comme indiqué dans la section précédente, certains types Windows Runtime peuvent apparaître en tant que types .NET dans les signatures des membres de votre composant, ou dans les signatures des membres Windows Runtime lorsque vous les utilisez dans l’IDE. Lorsque vous transmettez des types .NET à ces membres ou que vous les utilisez comme valeurs de retour des membres de votre composant, ils apparaissent dans le code de l’autre côté comme type Windows Runtime correspondant. Pour obtenir des exemples d’effets que cela peut avoir lorsque votre composant est appelé à partir de JavaScript, consultez la section « Retour de types managés à partir de votre composant » dans la procédure pas à pas de la création d’un composant C# ou Visual Basic Windows Runtime et de son appel à partir de JavaScript.
Méthodes surchargées
Dans Windows Runtime, les méthodes peuvent être surchargées. Toutefois, si vous déclarez plusieurs surcharges avec le même nombre de paramètres, vous devez appliquer l’attribut Windows.Foundation.Metadata.DefaultOverloadAttribute à une seule de ces surcharges. Cette surcharge est la seule que vous pouvez appeler à partir de JavaScript. Par exemple, dans le code suivant, la surcharge qui prend un int (Entier en Visual Basic) est la surcharge par défaut.
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
[IMPORTANT] JavaScript vous permet de transmettre n’importe quelle valeur à OverloadExample et de forcer la valeur au type requis par le paramètre. Vous pouvez appeler OverloadExample avec « quarante-deux », « 42 » ou 42.3, mais toutes ces valeurs sont passées à la surcharge par défaut. La surcharge par défaut dans l’exemple précédent retourne respectivement 0, 42 et 42.
Vous ne pouvez pas appliquer l’attribut DefaultOverloadAttribute aux constructeurs. Tous les constructeurs d’une classe doivent avoir différents nombres de paramètres.
Implémentation d’IStringable
À compter de Windows 8.1, Windows Runtime inclut une interface IStringable dont la méthode unique, IStringable.ToString, fournit une prise en charge de mise en forme de base comparable à celle fournie par Object.ToString. Si vous choisissez d’implémenter IStringable dans un type managé public exporté dans un composant Windows Runtime, les restrictions suivantes s’appliquent :
Vous pouvez définir l’interface IStringable uniquement dans une relation « class implements », comme le code suivant en C# :
public class NewClass : IStringable
Ou le code Visual Basic suivant :
Public Class NewClass : Implements IStringable
Vous ne pouvez pas implémenter IStringable sur une interface.
Vous ne pouvez pas déclarer de paramètre de type IStringable.
IStringable ne peut pas être le type de retour d’une méthode, d’une propriété ou d’un champ.
Vous ne pouvez pas masquer votre implémentation IStringable à partir de classes de base à l’aide d’une définition de méthode, par exemple :
public class NewClass : IStringable { public new string ToString() { return "New ToString in NewClass"; } }
Au lieu de cela, l’implémentation IStringable.ToString doit toujours remplacer l’implémentation de classe de base. Vous pouvez masquer une implémentation ToString uniquement en l’appelant sur une instance de classe fortement typée.
Remarque
Dans diverses conditions, les appels de code natif à un type managé qui implémente IStringable ou masque son implémentation de ToString peuvent produire un comportement inattendu.
Opérations asynchrones
Pour implémenter une méthode asynchrone dans votre composant, ajoutez « Async » à la fin du nom de la méthode et retournez l’une des interfaces Windows Runtime qui représentent des actions ou des opérations asynchrones : IAsyncAction, IAsyncActionWithProgress TProgress<>, IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult, TProgress.>
Vous pouvez utiliser des tâches .NET (classe Task et classe TResult> task<générique) pour implémenter votre méthode asynchrone. Vous devez retourner une tâche qui représente une opération en cours, telle qu’une tâche retournée à partir d’une méthode asynchrone écrite en C# ou Visual Basic, ou une tâche retournée par la méthode Task.Run. Si vous utilisez un constructeur pour créer la tâche, vous devez appeler sa méthode Task.Start avant de la renvoyer.
Une méthode qui utilise await
(Await
en Visual Basic) nécessite le async
mot clé (Async
en Visual Basic). Si vous exposez une telle méthode à partir d’un composant Windows Runtime, appliquez le async
mot clé au délégué que vous passez à la méthode Run .
Pour les actions et opérations asynchrones qui ne prennent pas en charge les rapports d’annulation ou de progression, vous pouvez utiliser la méthode d’extension WindowsRuntimeSystemExtensions.AsAsyncAction ou AsAsyncOperation<TResult> pour encapsuler la tâche dans l’interface appropriée. Par exemple, le code suivant implémente une méthode asynchrone à l’aide de la méthode Task.Run<TResult> pour démarrer une tâche. La méthode d’extension TResult> AsAsyncOperation<retourne la tâche en tant qu’opération asynchrone 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
Le code JavaScript suivant montre comment la méthode peut être appelée à l’aide d’un objet WinJS.Promise . La fonction transmise à la méthode ensuite est exécutée lorsque l’appel asynchrone se termine. Le paramètre stringList contient la liste des chaînes retournées par la méthode DownloadAsStringAsync et la fonction effectue le traitement requis.
function asyncExample(id) {
var result = SampleComponent.Example.downloadAsStringAsync(id).then(
function (stringList) {
// Place code that uses the returned list of strings here.
});
}
Pour les actions et opérations asynchrones qui prennent en charge les rapports d’annulation ou de progression, utilisez la classe AsyncInfo pour générer une tâche démarrée et connecter les fonctionnalités de création de rapports d’annulation et de progression de la tâche avec les fonctionnalités de création de rapports d’annulation et de progression de l’interface Windows Runtime appropriée. Pour obtenir un exemple qui prend en charge les rapports d’annulation et de progression, consultez la procédure pas à pas de création d’un composant C# ou Visual Basic Windows Runtime et l’appel à partir de JavaScript.
Notez que vous pouvez utiliser les méthodes de la classe AsyncInfo même si votre méthode asynchrone ne prend pas en charge les rapports d’annulation ou de progression. Si vous utilisez une fonction lambda Visual Basic ou une méthode anonyme C#, ne fournissez pas de paramètres pour le jeton et l’interface IProgress<T> . Si vous utilisez une fonction lambda C#, fournissez un paramètre de jeton, mais ignorez-le. L’exemple précédent, qui a utilisé la méthode TResult AsAsyncOperation<, ressemble à ceci lorsque vous utilisez plutôt la surcharge de méthode 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 vous créez une méthode asynchrone qui prend éventuellement en charge les rapports d’annulation ou de progression, envisagez d’ajouter des surcharges qui n’ont pas de paramètres pour un jeton d’annulation ou l’interface IProgress<T> .
Levée des exceptions
Vous pouvez lever n’importe quel type d’exception inclus dans les applications .NET pour Windows. Vous ne pouvez pas déclarer vos propres types d’exceptions publiques dans un composant Windows Runtime, mais vous pouvez déclarer et lever des types non publics.
Si votre composant ne gère pas l’exception, une exception correspondante est levée dans le code qui a appelé votre composant. La façon dont l’exception apparaît à l’appelant dépend de la façon dont la langue d’appel prend en charge Windows Runtime.
En JavaScript, l’exception apparaît sous la forme d’un objet dans lequel le message d’exception est remplacé par une trace de pile. Lorsque vous déboguez votre application dans Visual Studio, vous pouvez voir le texte du message d’origine affiché dans la boîte de dialogue d’exception du débogueur, identifié comme « Informations WinRT ». Vous ne pouvez pas accéder au texte du message d’origine à partir du code JavaScript.
Conseil. Actuellement, la trace de pile contient le type d’exception managé, mais nous vous déconseillons d’analyser la trace pour identifier le type d’exception. Utilisez plutôt une valeur HRESULT comme décrit plus loin dans cette section.
En C++, l’exception apparaît en tant qu’exception de plateforme. Si la propriété HResult de l’exception managée peut être mappée à HRESULT d’une exception de plateforme spécifique, l’exception spécifique est utilisée ; sinon, une exception Platform ::COMException est levée. Le texte du message de l’exception managée n’est pas disponible pour le code C++. Si une exception de plateforme spécifique a été levée, le texte du message par défaut pour ce type d’exception s’affiche ; sinon, aucun texte de message n’apparaît. Voir Exceptions (C++/CX).
En C# ou Visual Basic, l’exception est une exception managée normale.
Lorsque vous lèvez une exception à partir de votre composant, vous pouvez faciliter la gestion de l’exception par un appelant JavaScript ou C++ en lisant un type d’exception non public dont la valeur de propriété HResult est spécifique à votre composant. HRESULT est disponible pour un appelant JavaScript via la propriété number de l’objet d’exception et pour un appelant C++ via la propriété COMException ::HResult.
Remarque
Utilisez une valeur négative pour votre HRESULT. Une valeur positive est interprétée comme réussie et aucune exception n’est levée dans l’appelant JavaScript ou C++.
Déclaration et déclenchement d’événements
Lorsque vous déclarez un type pour contenir les données de votre événement, dérivez d’Object au lieu d’EventArgs, car EventArgs n’est pas un type Windows Runtime. Utilisez EventHandler<TEventArgs> comme type de l’événement et utilisez votre type d’argument d’événement comme argument de type générique. Déclenchez l’événement comme vous le feriez dans une application .NET.
Lorsque votre composant Windows Runtime est utilisé à partir de JavaScript ou C++, l’événement suit le modèle d’événement Windows Runtime attendu par ces langages. Lorsque vous utilisez le composant en C# ou Visual Basic, l’événement apparaît sous la forme d’un événement .NET ordinaire. Un exemple est fourni dans la procédure pas à pas de la création d’un composant Windows Runtime C# ou Visual Basic et de son appel à partir de JavaScript.
Si vous implémentez des accesseurs d’événements personnalisés (déclarez un événement avec le mot clé personnalisé , en Visual Basic), vous devez suivre le modèle d’événement Windows Runtime dans votre implémentation. Consultez les événements personnalisés et les accesseurs d’événements dans les composants Windows Runtime. Notez que lorsque vous gérez l’événement à partir du code C# ou Visual Basic, il semble toujours être un événement .NET ordinaire.
Étapes suivantes
Une fois que vous avez créé un composant Windows Runtime pour votre propre utilisation, vous pouvez constater que la fonctionnalité qu’elle encapsule est utile pour d’autres développeurs. Vous avez deux options pour empaqueter un composant pour la distribution à d’autres développeurs. Consultez Distribution d’un composant Windows Runtime managé.
Pour plus d’informations sur les fonctionnalités de langage Visual Basic et C# et la prise en charge de .NET pour Windows Runtime, consultez la documentation Visual Basic et C# .
Dépannage
Symptôme | Solution |
---|---|
Dans une application C++/WinRT, lors de l’utilisation d’un composant Windows Runtime C# qui utilise XAML, le compilateur génère une erreur au format « 'MyNamespace_XamlTypeInfo' : n’est pas un membre de 'winrt::MyNamespace' », où MyNamespace est le nom de l’espace de noms du composant Windows Runtime. | Dans pch.h , dans l’application qui utilise C++/WinRT, ajoutez #include <winrt/MyNamespace.MyNamespace_XamlTypeInfo.h> pour remplacer MyNamespace de manière appropriée. |