Passer de C# à C++/WinRT

Conseil

Si vous avez déjà lu cette rubrique et que vous la consultez de nouveau dans l’optique d’une tâche particulière, vous pouvez passer à la section Rechercher du contenu relatif à la tâche à effectuer de cette rubrique.

Cette rubrique catalogue tous les détails techniques impliqués dans le portage du code source dans un projet C# vers son équivalent en C++/WinRT.

Pour une étude de cas concernant le portage de l’un des exemples d’application UWP (plateforme Windows universelle), consultez la rubrique complémentaire Portage de l’exemple Clipboard vers C++/WinRT à partir de C#. Vous pouvez bénéficier d’une pratique et d’une expérience de portage en suivant cette procédure pas à pas et en portant l’exemple pour vous-même au fur et à mesure.

Comment se préparer et à quoi s’attendre

L’étude de cas Portage de l’exemple Clipboard vers C++/WinRT à partir de C# présente les types de décisions que vous devrez prendre en matière de conception logicielle lors du portage d’un projet vers C++/WinRT. Il est donc judicieux de se préparer au portage en acquérant de solides connaissances du fonctionnement du code existant. De cette façon, vous aurez une bonne idée de la fonctionnalité de l’application et de la structure du code, et les décisions que vous prendrez vous feront toujours aller de l’avant et dans la bonne voie.

Vous pouvez regrouper les changements de portage à prévoir en quatre catégories.

  • Porter la projection du langage. Le Windows Runtime (WinRT) est projeté dans différents langages de programmation. Chacune de ces projections de langage est conçue pour être idiomatique dans le langage de programmation en question. Pour C#, certains types Windows Runtime sont projetés en tant que types .NET. Par exemple, System.Collections.Generic.IReadOnlyList<T> sera traduit en Windows.Foundation.Collections.IVectorView<T>. Par ailleurs, en C#, certaines opérations Windows Runtime sont projetées en tant que fonctionnalités du langage C# par souci pratique. Par exemple, en C#, vous utilisez la syntaxe d’opérateur += pour inscrire un délégué de gestion d’événements. Vous allez donc traduire des fonctionnalités de langage comme celle-ci vers l’opération fondamentale effectuée (dans cet exemple, l’inscription d’événements).
  • Porter la syntaxe du langage. Bon nombre de ces changements sont de simples transformations mécaniques consistant à remplacer un symbole par un autre. Citons par exemple le remplacement d’un point (.) par deux signes deux-points (::).
  • Porter la procédure de langage. Certains de ces changements peuvent être simples et répétitifs (comme remplacer myObject.MyProperty par myObject.MyProperty()). D’autres peuvent être plus approfondis (comme porter une procédure impliquant l’utilisation de System.Text.StringBuilder vers une autre impliquant l’utilisation de std::wostringstream).
  • Tâches relatives au portage spécifiques à C++/WinRT. Certains détails de Windows Runtime sont pris en charge implicitement par C# en arrière-plan. Ces détails sont définis explicitement en C++/WinRT. Par exemple, vous utilisez un fichier .idl pour définir vos classes runtime.

La liste ci-dessous est organisée par tâche, et les autres sections de cette rubrique suivent la taxonomie ci-dessus.

Rechercher du contenu relatif à la tâche à effectuer

Tâche Content
Créer un composant Windows Runtime (WRC) Certaines fonctionnalités sont disponibles (ou certaines API sont appelées) uniquement avec C++. Vous pouvez factoriser ces fonctionnalités dans un composant WRC C++/WinRT, puis consommer le composant WRC à partir d’une application C#, par exemple. Consultez Composants Windows Runtime avec C++/WinRT et Si vous créez une classe runtime dans un composant Windows Runtime.
Porter une méthode async Il est conseillé de mettre auto lifetime = get_strong(); en première ligne d’une méthode asynchrone dans une classe runtime C++/WinRT (consultez Accès sécurisé au pointeur this dans une coroutine de membre de classe).

Pour le portage à partir de Task, consultez Action async.
Pour le portage à partir de Task<T>, consultez Opération async.
Pour le portage à partir de async void, consultez Méthode fire-and-forget.
Porter une classe Tout d’abord, déterminez si la classe doit être une classe runtime, ou si elle peut être une classe ordinaire. Pour vous aider, reportez-vous au début de la rubrique Créer des API avec C++/WinRT. Lisez ensuite les trois lignes ci-dessous.
Porter une classe runtime Classe qui partage des fonctionnalités en dehors de l’application C++, ou classe qui est utilisée dans une liaison de données XAML. Consultez Si vous créez une classe runtime dans un composant Windows Runtime ou Si vous créez une classe runtime qui sera référencée dans votre interface utilisateur XAML.

Ces liens décrivent cela plus en détail, mais une classe runtime doit être déclarée dans un fichier IDL. Si votre projet contient déjà un fichier IDL (par exemple, Project.idl), nous vous recommandons de déclarer toute nouvelle classe runtime dans ce fichier. Dans le fichier IDL, déclarez les méthodes et les membres de données qui seront utilisés en dehors de votre application ou dans une interface XAML. Après avoir mis à jour le fichier IDL, regénérez et examinez les fichiers stub créés (.h et .cpp) dans le dossier Generated Files de votre projet (dans l’Explorateur de solutions, avec le nœud du projet sélectionné, vérifiez que l’option Afficher tous les fichiers est activée). Comparez les fichiers stub aux fichiers déjà présents dans votre projet et, si cela est nécessaire, ajoutez des fichiers ou ajoutez/mettez à jour les signatures de fonction. La syntaxe des fichiers stub est toujours correcte. Nous vous recommandons donc de l’utiliser pour réduire les erreurs de build. Quand les stubs dans votre projet correspondent à ceux figurant dans les fichiers stub, vous pouvez les implémenter en portant le code C#.
Porter une classe ordinaire Consultez Si vous ne créez pas de classe runtime.
Créer un fichier IDL Présentation de Microsoft Interface Definition Language 3.0
Si vous créez une classe runtime qui sera référencée dans votre interface utilisateur XAML
Utilisation d’objets à partir du balisage XAML
Définir vos classes runtime dans IDL
Porter une collection Collections avec C++/WinRT
Mise à disposition d’une source de données pour le balisage XAML
Conteneur associatif
Accès aux membres d’un vecteur
Porter un événement Délégué de gestionnaire d’événements en tant que membre de classe
Révoquer un délégué de gestionnaire d’événements
Porter une méthode À partir de C# : private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
Vers le fichier .h C++/WinRT : fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
Vers le fichier .cpp C++/WinRT : fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Porter des chaînes Gestion des chaînes en C++/WinRT
ToString
Génération de chaîne
Conversions boxing et unboxing d’une chaîne
Convertir des types (cast de types) C#: o.ToString()
C++/WinRT : to_hstring(static_cast<int>(o))
Voir aussi ToString.

C#: (Value)o
C++/WinRT : unbox_value<Value>(o)
Lève une exception si la conversion unboxing échoue. Voir aussi Conversions boxing et unboxing.

C#: o as Value? ?? fallback
C++/WinRT : unbox_value_or<Value>(o, fallback)
Retourne fallback si la conversion unboxing échoue. Voir aussi Conversions boxing et unboxing.

C#: (Class)o
C++/WinRT : o.as<Class>()
Lève une exception si la conversion échoue.

C#: o as Class
C++/WinRT : o.try_as<Class>()
Retourne null si la conversion échoue.

Modifications qui impliquent la projection du langage

Category C# C++/WinRT Voir aussi
Objet non typé object ou System.Object Windows::Foundation::IInspectable Portage de la méthode EnableClipboardContentChangedNotifications
Espaces de noms de projection using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Taille d’une collection collection.Count collection.Size() Portage de la méthode BuildClipboardFormatsOutputString
Type de collection classique IList<T>, et Add pour ajouter un élément. IVector<T>, et Append pour ajouter un élément. Si vous utilisez un std::vector quelque part, utilisez push_back pour ajouter un élément.
Type de collection en lecture seule IReadOnlyList<T> IVectorView<T> Portage de la méthode BuildClipboardFormatsOutputString
Délégué de gestionnaire d’événements en tant que membre de classe myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Portage de la méthode EnableClipboardContentChangedNotifications
Révoquer un délégué de gestionnaire d’événements myObject.EventName -= Handler; myObject.EventName(token); Portage de la méthode EnableClipboardContentChangedNotifications
Conteneur associatif IDictionary<K, V> IMap<K, V>
Accès aux membres d’un vecteur x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Inscrire/révoquer un gestionnaire d’événements

En C++/WinRT, vous disposez de plusieurs options syntaxiques pour inscrire/révoquer un délégué de gestionnaire d’événements, comme décrit dans Gérer des événements en utilisant des délégués en C++/WinRT. Consultez également Portage de la méthode EnableClipboardContentChangedNotifications.

Par exemple, quand un destinataire d’événement (objet gérant un événement) est sur le point d’être détruit, vous devrez parfois révoquer un gestionnaire d’événements pour que la source de l’événement (objet déclenchant l’événement) n’effectue pas d’appel dans l’objet détruit. Consultez Révoquer un délégué inscrit. Dans des cas comme celui-ci, créez une variable membre event_token pour vos gestionnaires d’événements. Pour obtenir un exemple, consultez Portage de la méthode EnableClipboardContentChangedNotifications.

Vous pouvez également inscrire un gestionnaire d’événements dans le balisage XAML.

<Button x:Name="OpenButton" Click="OpenButton_Click" />

Dans C#, même si votre méthode OpenButton_Click est privée, XAML sera toujours en mesure de la connecter à l’événement ButtonBase.Click déclenché par OpenButton.

En C++/WinRT, votre méthode OpenButton_Click doit être publique dans votre type d’implémentation si vous souhaitez l’inscrire dans le balisage XAML. Si vous enregistrez un gestionnaire d’événements uniquement en code impératif, le gestionnaire d’événements n’a pas besoin d’être public.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

Vous pouvez également faire en sorte que la page XAML d’inscription soit l’amie de votre type d’implémentation et définir OpenButton_Click en méthode privée.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
    private:
        friend MyPageT;
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

Dernier scénario, le projet C# que vous portez est lié au gestionnaire d’événements à partir du balisage (pour plus de détails sur ce scénario, consultez Fonctions dans x:Bind).

<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />

Vous pouvez juste remplacer ce balisage par le Click="OpenButton_Click" plus simple. Ou, si vous préférez, vous pouvez conserver ce balisage tel quel. Pour le prendre en charge, il vous suffit de déclarer le gestionnaire d’événements dans l’IDL.

void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);

Notes

Déclarez la fonction comme void même si vous l’implémentez comme Déclencher et oublier.

Modifications qui impliquent la syntaxe du langage

Category C# C++/WinRT Voir aussi
Modificateurs d’accès public \<member\> public:
    \<member\>
Portage de la méthode Button_Click
Accéder à un membre de données this.variable this->variable  
Action asynchrone async Task ... IAsyncAction ... Interface IAsyncAction, Opérations concurrentes et asynchrones avec C++/WinRT
Opération asynchrone async Task<T> ... IAsyncOperation<T> ... Interface IAsyncOperation, Opérations concurrentes et asynchrones avec C++/WinRT
Méthode « fire-and-forget » ou « déclencher et oublier » (implique async) async void ... winrt::fire_and_forget ... Porter la méthode CopyButton_Click, fire-and-forget
Accéder à une constante énumérée E.Value E::Value Portage de la méthode DisplayChangedFormats
Attente coopérative await ... co_await ... Portage de la méthode CopyButton_Click
Collection de types projetés en tant que champ privé private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
Construction 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} };
Séparateur d’espace de noms A.B.T A::B::T
Null null nullptr Portage de la méthode UpdateStatus
Obtenir un objet de type typeof(MyType) winrt::xaml_typename<MyType>() Portage de la propriété Scenarios
Déclaration de paramètre pour une méthode MyType MyType const& Passage de paramètres
Déclaration de paramètre pour une méthode async MyType MyType Passage de paramètres
Appeler une méthode statique T.Method() T::Method()
Chaînes string ou System.String winrt::hstring Gestion des chaînes en C++/WinRT
Littéral de chaîne "a string literal" L"a string literal" Portage du constructeur, de Current et de FEATURE_NAME
Type inféré (ou déduit) var auto Portage de la méthode BuildClipboardFormatsOutputString
Directive using using A.B.C; using namespace A::B::C; Portage du constructeur, de Current et de FEATURE_NAME
Littéral de chaîne textuelle/brute @"verbatim string literal" LR"(raw string literal)" Portage de la méthode DisplayToast

Notes

Si un fichier d’en-tête ne contient pas de directive using namespace pour un espace de noms donné, vous devez qualifier complètement tous les noms de types de cet espace de noms ou au moins les qualifier suffisamment pour que le compilateur puisse les trouver. Pour obtenir un exemple, consultez Portage de la méthode DisplayToast.

Portage des classes et des membres

Pour chaque type C#, vous devez décider si vous souhaitez le porter vers un type Windows Runtime ou vers une classe, un struct ou une énumération C++ normal. Pour obtenir plus d’informations et des exemples détaillés montrant comment prendre ces décisions, consultez Portage de l’exemple Clipboard vers C++/WinRT à partir de C#.

Une propriété C# devient généralement une fonction d’accesseur, une fonction de mutateur et un membre de données de stockage. Pour obtenir plus d’informations et un exemple, consultez Portage de la propriété IsClipboardContentChangedEnabled.

Configurez les champs non statiques en membres de données de votre type d’implémentation.

Un champ statique C# devient une fonction de mutateur et/ou d’accesseur statique C++/WinRT. Pour obtenir plus d’informations et un exemple, consultez Portage du constructeur, de Current et de FEATURE_NAME.

Là encore, pour chaque fonction membre, vous devez décider si elle appartient ou non à l’IDL ou si elle constitue une fonction membre publique ou privée de votre type d’implémentation. Pour obtenir plus d’informations et des exemples illustrant le processus de décision, consultez IDL pour le type MainPage.

Portage du balisage XAML et des fichiers de ressources

Dans le cas du portage de l’exemple Clipboard vers C++/WinRT à partir de C#, nous avons pu utiliser un balisage XAML (ressources comprises) et des fichiers de ressources identiques dans l’ensemble du projet C# et C++/WinRT. Dans certains cas, des modifications du balisage sont nécessaires. Consultez Copier le XAML et les styles nécessaires pour terminer le portage de MainPage.

Modifications qui impliquent des procédures dans le langage

Category C# C++/WinRT Voir aussi
Gestion de la durée de vie dans une méthode async NON APPLICABLE auto lifetime{ get_strong() }; ou
auto lifetime = get_strong();
Portage de la méthode CopyButton_Click
Cession using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Portage de la méthode CopyImage
Construire un objet new MyType(args) MyType{ args } ou
MyType(args)
Portage de la propriété Scenarios
Créer une référence non initialisée MyType myObject; MyType myObject{ nullptr }; ou
MyType myObject = nullptr;
Portage du constructeur, de Current et de FEATURE_NAME
Construire un objet dans une variable avec des arguments var myObject = new MyType(args); auto myObject{ MyType{ args } }; ou
auto myObject{ MyType(args) }; ou
auto myObject = MyType{ args }; ou
auto myObject = MyType(args); ou
MyType myObject{ args }; ou
MyType myObject(args);
Portage de la méthode Footer_Click
Construire un objet dans une variable sans arguments var myObject = new T(); MyType myObject; Portage de la méthode BuildClipboardFormatsOutputString
Raccourci de l’initialisation d’objets var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Opération de vectorisation en bloc var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Portage de la méthode CopyButton_Click
Itérer au sein de la collection foreach (var v in c) for (auto&& v : c) Portage de la méthode BuildClipboardFormatsOutputString
Intercepter une exception catch (Exception ex) catch (winrt::hresult_error const& ex) Portage de la méthode PasteButton_Click
Détails de l’exception ex.Message ex.message() Portage de la méthode PasteButton_Click
Obtenir une valeur de propriété myObject.MyProperty myObject.MyProperty() Portage de la méthode NotifyUser
Définir une valeur de propriété myObject.MyProperty = value; myObject.MyProperty(value);
Incrémenter une valeur de propriété myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
Pour les chaînes, basculer vers un générateur
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Chaîne de langage en chaîne Windows Runtime NON APPLICABLE winrt::hstring{ s }
Génération de chaîne StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Génération de chaîne
Interpolation de chaîne $"{i++}) {s.Title}" winrt::to_hstring et/ou winrt::hstring::operator+ Portage de la méthode OnNavigatedTo
Chaîne vide pour la comparaison System.String.Empty winrt::hstring::empty Portage de la méthode UpdateStatus
Créer une chaîne vide var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
Opérations de dictionnaire map[k] = v; // replaces any existing
v = map[k]; // throws if not present
map.ContainsKey(k)
map.Insert(k, v); // replaces any existing
v = map.Lookup(k); // throws if not present
map.HasKey(k)
Conversion de type (lever en cas d’échec) (MyType)v v.as<MyType>() Portage de la méthode Footer_Click
Conversion de type (null en cas d’échec) v as MyType v.try_as<MyType>() Portage de la méthode PasteButton_Click
Les éléments XAML avec x:Name sont des propriétés MyNamedElement MyNamedElement() Portage du constructeur, de Current et de FEATURE_NAME
Basculer vers le thread d’interface utilisateur CoreDispatcher.RunAsync CoreDispatcher.RunAsync ou winrt::resume_foreground Portage de la méthode NotifyUser et Portage de la méthode HistoryAndRoaming
Construction d’élément d’interface utilisateur dans le code impératif d’une page XAML Voir Construction d’élément d’interface utilisateur Voir Construction d’élément d’interface utilisateur

Les sections suivantes décrivent plus en détail certains des éléments du tableau.

Construction d’élément d’interface utilisateur

Ces exemples de code illustrent la construction d’un élément d’interface utilisateur dans le code impératif d’une page XAML.

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Windows::UI::Xaml::Style>(
        Resources().Lookup(
            winrt::box_value(L"MyTextBlockStyle")
        )
    )
);

ToString()

Les types C# fournissent la méthode Object::ToString.

int i = 2;
var s = i.ToString(); // s is a System.String with value "2".

C++/WinRT ne fournit pas directement cette fonctionnalité, mais vous disposez d’alternatives.

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

C++/WinRT prend également en charge winrt::to_hstring pour certains types. Vous devez ajouter des surcharges pour tous les types supplémentaires que vous souhaitez convertir en chaîne.

Language Convertir un entier en chaîne Convertir une valeur enum en chaîne
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);

Si vous souhaitez convertir une valeur enum en chaîne, vous devez fournir l’implémentation 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));
        }
    }
}

Ces conversions en chaîne sont souvent utilisées implicitement par la liaison de données.

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

Ces liaisons effectuent la conversion winrt::to_hstring de la propriété liée. Pour le deuxième exemple (StatusEnum), vous devez fournir votre propre surcharge de winrt::to_hstring. Dans le cas contraire, vous recevrez une erreur relative au compilateur.

Consultez également Portage de la méthode Footer_Click.

Génération de chaîne

Pour la génération de chaînes, C# possède un type StringBuilder intégré.

Category C# C++/WinRT
Génération de chaîne StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Ajouter une chaîne Windows Runtime en préservant les valeurs null builder.Append(s); builder << std::wstring_view{ s };
Ajouter une nouvelle ligne builder.Append(Environment.NewLine); builder << std::endl;
Accéder au résultat s = builder.ToString(); ws = builder.str();

Consultez également Portage de la méthode BuildClipboardFormatsOutputString et Portage de la méthode DisplayChangedFormats.

Exécution de code sur le thread d’interface utilisateur principal

Cet exemple est tiré de l’exemple de scanneur de codes-barres.

Lorsque vous souhaitez travailler sur le thread d’interface utilisateur principal dans un projet C#, vous utilisez généralement la méthode CoreDispatcher.RunAsync, comme ceci.

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Do work on the main UI thread here.
    });
}

Il est bien plus simple d’exprimer cela en C++/WinRT. Notez que nous acceptons les paramètres par valeur en supposant que nous allons vouloir y accéder après le premier point de suspension (co_await, dans le cas présent). Pour plus d’informations, consultez Passage de paramètres.

winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
    co_await Dispatcher();
    // Do work on the main UI thread here.
}

Si vous devez effectuer le travail à une priorité différente de celle par défaut, consultez la fonction winrt::resume_foreground, qui a une surcharge qui est prioritaire. Pour obtenir des exemples de code montrant comment attendre un appel à winrt::resume_foreground, consultez Programmation en tenant compte de l’affinité des threads.

Définir vos classes runtime dans IDL

Consultez IDL pour le type MainPage et Consolider vos fichiers .idl.

Inclure les fichiers d’en-tête de l’espace de noms Windows C++/WinRT nécessaires

En C++/WinRT, chaque fois que vous voulez utiliser un type à partir d’un espace de noms Windows, vous devez inclure le fichier d’en-tête de l’espace de noms Windows C++/WinRT correspondant. Pour obtenir un exemple, consultez Portage de la méthode NotifyUser.

Boxing et unboxing

C# effectue automatiquement une conversion boxing des scalaires en objets. C++/WinRT vous oblige à appeler la fonction winrt::box_value de manière explicite. Les deux langages nécessitent un unboxing explicite. Consultez Conversions boxing et unboxing avec C++/WinRT.

Dans les tableaux suivants, nous allons utiliser ces définitions.

C# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
Opération C# C++/WinRT
Boxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Unboxing i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX et C# génèrent des exceptions si vous essayez d’effectuer une conversion unboxing d’un pointeur null en un type de valeur. C++/WinRT considère qu’il s’agit d’une erreur de programmation, ce qui provoque un blocage. Dans C++/WinRT, utilisez la fonction winrt::unbox_value_or si vous souhaitez prendre en charge le cas avec l’objet qui n’est pas du type que vous pensiez.

Scénario C# C++/WinRT
Conversion unboxing d’un entier connu i = (int)o; i = unbox_value<int>(o);
Si o est null System.NullReferenceException Se bloquer
Si o n’est pas un entier converti par boxing System.InvalidCastException Se bloquer
Conversion unboxing d’un entier, utiliser la valeur de secours si la valeur est null ; se bloquer si autre chose i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Conversion unboxing d’un entier si possible ; utiliser la valeur de secours pour toute autre chose i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Pour obtenir un exemple, consultez Portage de la méthode OnNavigatedTo et Portage de la méthode Footer_Click.

Conversions boxing et unboxing d’une chaîne

Une chaîne est parfois un type de valeur et parfois un type de référence. C# et C++/WinRT traitent les chaînes différemment.

Le type ABI HSTRING est un pointeur vers une chaîne avec décompte des références. Toutefois, il ne dérive pas à partir de IInspectable. Techniquement, il ne s’agit donc pas d’un objet. En outre, un pointeur HSTRING null représente la chaîne vide. La conversion boxing d’éléments non dérivés à partir de IInspectable est effectuée en les encapsulant dans une interface IReference<T>. De plus, Windows Runtime fournit une implémentation standard sous la forme de l’objet PropertyValue (les types personnalisés sont signalés sous la forme PropertyType::OtherType).

C# représente une chaîne Windows Runtime sous la forme d’un type de référence, tandis que C++/WinRT projette une chaîne sous la forme d’un type de valeur. Cela signifie qu’une chaîne null convertie par boxing peut avoir des représentations différentes selon la façon dont vous l’avez obtenue.

Comportement C# C++/WinRT
Déclarations object o;
string s;
IInspectable o;
hstring s;
Catégorie de type de chaîne Type de référence Type de valeur
HSTRING null projette sous la forme "" hstring{}
Est-ce que null et "" sont identiques ? Non Oui
Validité de la valeur null s = null;
s.Length génère NullReferenceException
s = hstring{};
s.size() == 0 (valide)
Si vous affectez une chaîne null à l’objet o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
Si vous affectez "" à l’objet o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

Boxing et unboxing de base.

Opération C# C++/WinRT
Conversion boxing d’une chaîne o = s;
La chaîne vide devient un objet non null.
o = box_value(s);
La chaîne vide devient un objet non null.
Conversion unboxing d’une chaîne connue s = (string)o;
L’objet null devient une chaîne null.
InvalidCastException si ce n’est pas une chaîne.
s = unbox_value<hstring>(o);
L’objet null plante.
Plantage si ce n’est pas une chaîne.
Unboxing d’une chaîne possible s = o as string;
L’objet null ou la non-chaîne devient une chaîne null.

OU

s = o as string ?? fallback;
Null ou non-chaîne devient la valeur de secours.
Chaîne vide conservée.
s = unbox_value_or<hstring>(o, fallback);
Null ou non-chaîne devient la valeur de secours.
Chaîne vide conservée.

Mise à disposition d’une classe pour l’extension de balisage {Binding}

Si vous envisagez d’utiliser l’extension de balisage {Binding} pour lier des données à votre type de données, consultez Objet de liaison déclaré à l’aide de {Binding}.

Utilisation d’objets à partir du balisage XAML

Dans un projet C#, vous pouvez utiliser des éléments nommés et des membres privés à partir du balisage XAML. Toutefois, dans C++/WinRT, toutes les entités utilisées via l’extension de balisage {x:Bind} XAML doivent être exposées publiquement dans IDL.

En outre, une liaison à un booléen affiche true ou false dans C#, mais affichera Windows.Foundation.IReference`1<Boolean> dans C++/WinRT.

Pour obtenir plus d’informations et d’exemples de code, consultez Utilisation d’objets à partir du balisage.

Mise à disposition d’une source de données pour le balisage XAML

Dans C++/WinRT 2.0.190530.8 et ultérieur, winrt::single_threaded_observable_vector crée un vecteur observable qui prend en charge IObservableVector<T> et IObservableVector<IInspectable>. Pour obtenir un exemple, consultez Portage de la propriété Scenarios.

Vous pouvez créer votre fichier Midl (.idl) de cette façon (consultez Factorisation des classes runtime dans des fichiers Midl (.idl)).

namespace Bookstore
{
    runtimeclass BookSku { ... }

    runtimeclass BookstoreViewModel
    {
        Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
    }

    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Ensuite, vous l’implémentez comme suit.

// 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;
};
...

Pour plus d’informations, consultez Contrôles d’éléments XAML ; liaison à une collection C++/WinRT et Collections avec C++/WinRT.

Mise à disposition d’une source de données pour le balisage XAML (version antérieure à C++/WinRT 2.0.190530.8)

La liaison de données XAML exige qu’une source d’éléments implémente IIterable<IInspectable>, ainsi que l’une des combinaisons d’interfaces suivantes.

  • IObservableVector<IInspectable>
  • IBindableVector et INotifyCollectionChanged
  • IBindableVector et IBindableObservableVector
  • IBindableVector uniquement (ne répond pas aux modifications)
  • IVector<IInspectable>
  • IBindableIterable (effectue une itération et enregistre les éléments dans une collection privée)

Une interface générique comme IVector<T> ne peut pas être détectée au moment de l’exécution. Chaque IVector<T> a une fonction T qui correspond à un identificateur d’interface (IID) différent. Les développeurs peuvent développer l’ensemble de fonctions T de manière arbitraire. Le code de liaison XAML ne peut donc jamais connaître l’ensemble complet à interroger. Cette restriction n’est pas un problème pour C#, car chaque objet CLR implémentant IEnumerable<T> implémente automatiquement IEnumerable. Au niveau de l’ABI, cela signifie que chaque objet implémentant IObservableVector<T> implémente automatiquement IObservableVector<IInspectable>.

C++/WinRT ne propose pas cette garantie. Si une classe runtime C++/WinRT implémente IObservableVector<T>, nous ne pouvons pas partir du principe qu’une implémentation IObservableVector<IInspectable> est également fournie.

Par conséquent, voici à quoi doit ressembler l’exemple précédent.

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

Quant à l’implémentation :

// 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 vous avez besoin d’accéder à des objets dans m_bookSkus, vous devez appliquer en retour une méthode QI à 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;
}

Classes dérivées

La classe de base doit être composable pour être dérivée à partir d’une classe runtime. C# ne nécessite pas de procédure spéciale pour que vos classes soient composables, contrairement à C++/WinRT. Vous utilisez le mot clé unsealed pour indiquer que vous souhaitez que votre classe soit utilisable comme classe de base.

unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

Dans le fichier d’en-tête de votre type d’implémentation, vous devez ajouter le fichier d’en-tête de la classe de base avant d’inclure l’en-tête généré automatiquement pour la classe dérivée. Dans le cas contraire, vous obtiendrez des erreurs telles que « Utilisation non conforme de ce type comme expression ».

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

API importantes