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 plateforme Windows universelle applications (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 pour une utilisation uniquement dans des 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.

Notes

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 Windows Runtime composants

En interne, les types Windows Runtime dans votre composant peuvent utiliser n’importe quelle fonctionnalité .NET autorisée dans une application UWP. Pour plus d’informations, consultez .NET pour les applications UWP.

En externe, les membres de vos types peuvent exposer uniquement Windows Runtime types pour leurs paramètres et valeurs de retour. La liste suivante décrit les limitations sur les 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 de type Windows Runtime. Cette restriction inclut les types Windows Runtime que vous créez, ainsi que les types fournis par le 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 de .NET pour permettre l’utilisation naturelle des 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 Windows Runtime types. Cela est décrit dans les sections Passage de types Windows Runtime au code managé et Transmission de types managés à l’Windows Runtime, plus loin dans cette rubrique.

  • Les interfaces et classes publiques peuvent contenir des méthodes, propriétés et é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 ou une interface publique ne peut pas :

    • être 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 à partir de types qui ne sont pas dans le Windows Runtime, tels que System.Exception et System.EventArgs.
  • Tous les types publics doivent posséder un espace de noms racine qui correspond au nom de l’assembly, et le nom de l’assembly ne doit pas commencer par « Windows ».

    Pourboire. Par défaut, les projets Visual Studio ont des noms d'espaces de noms qui correspondent au nom de l'assembly. En Visual Basic, la déclaration d’espace de noms pour cet espace de noms par défaut n’est pas affichée dans votre code.

  • Les structures publiques ne peuvent pas avoir d’autres membres que les champs publics, et ces champs doivent être des types de valeur ou des chaînes.

  • Les classes publiques doivent être sealed (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 du code natif en même temps. La valeur par défaut est uniquement le code natif.

Pour déboguer à la fois du code C++ natif et du code managé

  1. Ouvrez le menu contextuel de votre projet Visual C++, puis choisissez Propriétés.
  2. Dans les pages de propriétés, sous Propriétés de configuration, choisissez Débogage.
  3. Choisissez Type de débogueur, et dans la zone de liste déroulante remplacez la valeur Natif uniquement par Mixte (managé et natif). Choisissez OK.
  4. Définissez des points d’arrêt dans le code natif et le code 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 du code JavaScript et du code managé en même temps.

Pour déboguer du code managé au lieu de JavaScript

  1. Ouvrez le menu contextuel de votre projet JavaScript, puis choisissez Propriétés.
  2. Dans les pages de propriétés, sous Propriétés de configuration, choisissez Débogage.
  3. Choisissez Type de débogueur, et dans la zone de liste déroulante remplacez la valeur Script uniquement par Managé uniquement. Choisissez OK.
  4. Définissez des points d’arrêt dans le code managé et déboguez comme d’habitude.

Passage de types Windows Runtime au code managé

Comme mentionné précédemment dans la section Déclaration de types dans Windows Runtime composants, certains types .NET peuvent apparaître dans les signatures des membres des classes publiques. Cela fait partie de la prise en charge de .NET pour permettre l’utilisation naturelle des Windows Runtime dans le code managé. Elle inclut des types primitifs et certaines classes et interfaces. Lorsque votre composant est utilisé à partir de JavaScript ou de code C++, il est important de savoir comment vos types .NET apparaissent à l’appelant. Pour obtenir des exemples avec JavaScript, consultez Procédure pas à pas de création d’un composant Windows Runtime C# ou Visual Basic et appel de celui-ci à partir de JavaScript. Cette section aborde les 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 structures et types primitifs dans le Windows Runtime ont uniquement des champs. Lorsque vous transmettez ces types à du code managé, ils semblent être des types .NET, et vous pouvez utiliser les propriétés et les méthodes des types .NET comme vous le feriez normalement. La liste suivante récapitule les substitutions effectuées automatiquement dans l’IDE :

  • Pour le Windows Runtime primitives 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 langage mot clé pour l’un de ces types, vous pouvez utiliser le langage mot clé à la place.

En plus des types primitifs, certains types de base couramment utilisés Windows Runtime 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 souhaitiez la passer à 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 déterminer 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. (En règle générale, les deux types portent 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érique les plus courants, ainsi que d’autres classes et mappages d’interface courants. Pour obtenir la liste complète des types Windows Runtime mappés par .NET, consultez 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 n’importe quelle interface qu’il implémente comme type de paramètre ou type de retour d’un membre. Par exemple, vous pouvez passer ou renvoyer un int de dictionnaire<, une chaîne> (Dictionary(Of Integer, String) en Visual Basic) en tant que 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 implémentées par un type managé. Par exemple, si vous retournez Dictionary<int, string> au code JavaScript, il apparaît sous la forme IDictionary<int, string>, quelle que soit l’interface que vous spécifiez comme type de retour. Cela signifie que si la première interface n’inclut pas un membre qui apparaît sur les interfaces ultérieures, ce membre n’est pas visible pour JavaScript.

Dans le 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 les forme IDictionary<TKey, TValue> et IReadOnlyDictionary<TKey, TValue>. Vous utilisez donc 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é sous la forme IDictionary<TKey, 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 ne semble pas avoir de méthode Insert . Vous pouvez voir cet exemple dans la rubrique Procédure pas à pas pour créer un composant C# ou Visual Basic Windows Runtime et l’appeler à partir de JavaScript.

Passage de types managés au 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 de Windows Runtime membres 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 pour le code de l’autre côté comme le type Windows Runtime correspondant. Pour obtenir des exemples des 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 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 le 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 depuis JavaScript. Par exemple, dans le code suivant la surcharge qui prend int (Integer 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 passer 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 des numéros de paramètres différents.

Implémentation d’IStringable

À compter de Windows 8.1, le Windows Runtime inclut une interface IStringable dont la méthode unique, IStringable.ToString, fournit une prise en charge de la 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 », telle que 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 un 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 des classes de base à l’aide d’une définition de méthode telle que la suivante :

    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 la classe de base. Vous pouvez masquer une implémentation ToString uniquement en l’appelant sur une classe fortement typée instance.

Notes

Dans diverses conditions, les appels du code natif à un type managé qui implémente IStringable ou masque son implémentation 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 opérations asynchrones : IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult, TProgress>.

Vous pouvez utiliser des tâches .NET (classe Task et classe Task<TResult> 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 à partir de 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 retourner.

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 l’annulation ou le rapport 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 qui est passée à la méthode then 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 nécessaire.

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 pour connecter les fonctionnalités de rapport d’annulation et de rapport d’avancement de la tâche avec les fonctionnalités d’annulation et de rapport d’avancement de l’interface Windows Runtime appropriée. Pour obtenir un exemple qui prend en charge les rapports d’annulation et de progression, consultez Procédure pas à pas pour créer un composant C# ou Visual Basic Windows Runtime et l’appeler à 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 l’annulation ou le rapport 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 T> IProgress<. Si vous utilisez une fonction lambda en C#, fournissez un paramètre de jeton, mais ignorez-le. L’exemple précédent, qui utilisait la méthode AsAsyncOperation<TResult> , ressemble à ceci lorsque vous utilisez la surcharge de méthode AsyncInfo.Run<TResult>(Func<CancellationToken, Task<TResult>>) à la place.

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 l’annulation ou la création de rapports de progression, envisagez d’ajouter des surcharges qui n’ont pas de paramètres pour un jeton d’annulation ou l’interface T> IProgress<.

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 publics 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 appelle votre composant. La façon dont l’exception s’affiche pour l’appelant dépend de la méthode dont son langage prend en charge Windows Runtime.

  • Dans JavaScript, l’exception s’affiche en tant qu’objet dans lequel le message d’exception est remplacé par une trace de la 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. La trace de la pile contient actuellement le type d'exception managé, mais nous ne vous recommandons pas 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 s’affiche comme une exception de plateforme. Si la propriété HResult de l’exception managée peut être mappée au 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 est levée, le texte du message par défaut pour ce type d’exception apparaît. Dans le cas contraire, 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 levez une exception de votre composant, vous pouvez permettre plus facilement à l’appelant JavaScript ou C++ de gérer l’exception en levant un type d’exception non public dont la valeur de la propriété HResult est spécifique à votre composant. HRESULT est disponible pour un appelant JavaScript via la propriété number de l’objet exception et pour un appelant C++ via la propriété COMException::HResult .

Notes

Utilisez une valeur négative pour votre HRESULT. Une valeur positive est interprétée comme une réussite et aucune exception n’est levée dans l’appelant JavaScript ou C++.

Déclaration et déclenchement des événements

Lorsque vous déclarez un type pour contenir les données de votre événement, dérivez de Object au lieu de 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 à partir de C# ou Visual Basic, l’événement apparaît sous la forme d’un événement .NET ordinaire. Un exemple est fourni dans Procédure pas à pas pour créer un composant C# ou Visual Basic Windows Runtime et l’appeler à partir de JavaScript.

Si vous implémentez les accesseurs d’événement personnalisés (si vous déclarez un événement avec le mot-clé Custom en Visual Basic), vous devez suivre le modèle d’événement Windows Runtime dans votre implémentation. Consultez Événements personnalisés et accesseurs d’événements dans Windows Runtime composants. Notez que lorsque vous gérez l’événement à partir de 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 constaterez peut-être que la fonctionnalité qu’il encapsule est utile pour d’autres développeurs. Vous avez deux possibilités pour empaqueter un composant afin de le distribuer à d’autres développeurs. Voir Distribution d’un composant Windows Runtime managé.

Pour plus d’informations sur les fonctionnalités du langage Visual Basic et C#, ainsi que sur la prise en charge de .NET pour le 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.