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
parmyObject.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() }; ouauto 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 } ouMyType(args) |
Portage de la propriété Scenarios |
Créer une référence non initialisée | MyType myObject; |
MyType myObject{ nullptr }; ouMyType 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.
Tâches relatives au portage spécifiques à C++/WinRT
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>
{
...
}
}