Passer de C++/CX à C++/WinRT

Cette rubrique est la première d’une série décrivant comment porter le code source dans votre projet C++/CX vers son équivalent en C++/WinRT.

Si votre projet utilise également les types de la bibliothèque de modèles C++ Windows Runtime (WRL), consultez Migrer de WRL vers C++/WinRT.

Stratégies de portage

Il est intéressant de savoir que le portage de C++/CX vers C++/WinRT est généralement simple, à l’exception du passage de tâches PPL (Parallel Patterns Library) aux coroutines. Les modèles sont différents. Il n’existe pas de mappage un-à-un naturel des tâches PPL aux coroutines, et il n’existe aucun moyen simple de porter mécaniquement le code qui fonctionne pour tous les cas. Pour obtenir de l’aide sur cet aspect spécifique du portage et sur vos options d’interopérabilité entre les deux modèles, consultez Asynchronie et interopérabilité entre C++/WinRT et C++/CX.

Les équipes de développement signalent régulièrement qu’une fois qu’elles sont sur le point de porter leur code asynchrone, le reste du travail de portage est en grande partie mécanique.

Transfert en une seule opération

Si vous êtes en mesure de porter l’intégralité de votre projet en une seule passe, vous n’aurez besoin que de cette rubrique pour les informations dont vous avez besoin (et vous n’aurez pas besoin des rubriques d’interopérabilité qui suivent celle-ci). Nous vous recommandons de commencer par créer un projet dans Visual Studio à l’aide de l’un des modèles de projet C++/WinRT (consultez Visual Studio prise en charge de C++/WinRT). Déplacez ensuite vos fichiers de code source vers ce nouveau projet, puis transférez tout le code source C++/CX vers C++/WinRT comme vous le faites.

Sinon, si vous préférez effectuer le portage dans votre projet C++/CX existant, vous devez y ajouter la prise en charge de C++/WinRT. Les étapes que vous suivez pour ce faire sont décrites dans Prise d’un projet C++/CX et ajout de la prise en charge de C++/WinRT. Au moment où vous avez terminé le portage, vous aurez transformé ce qui était un projet C++/CX pur en un projet C++/WinRT pur.

Note

Si vous avez un projet de composant Windows Runtime, le portage d’une seule passe est votre seule option. Un projet de composant Windows Runtime écrit en C++ doit contenir tout le code source C++/CX ou tout le code source C++/WinRT. Ils ne peuvent pas coexister dans ce type de projet.

Portage progressif d’un projet

À l'exception de Windows Runtime projets de composants, comme indiqué dans la section précédente, si la taille ou la complexité de votre codebase rend nécessaire le portage progressif de votre projet, vous aurez besoin d'un processus de portage dans lequel le code C++/CX et C++/WinRT existe côte à côte dans le même projet. En plus de lire cette rubrique, consultez également Interop entre C++/WinRT et C++/CX et Asynchrony, et interopérabilité entre C++/WinRT et C++/CX. Ces rubriques fournissent des informations et des exemples de code montrant comment interagir entre les deux projections de langage.

Pour préparer un projet pour un processus de portage progressif, une option consiste à ajouter la prise en charge de C++/WinRT à votre projet C++/CX. Les étapes que vous suivez pour ce faire sont décrites dans Prise d’un projet C++/CX et ajout de la prise en charge de C++/WinRT. Vous pourrez ensuite migrer progressivement.

Une autre option consiste à créer un projet dans Visual Studio à l’aide de l’un des modèles de projet C++/WinRT (consultez Visual Studio prise en charge de C++/WinRT). Ajoutez ensuite la prise en charge de C++/CX à ce projet. Les étapes que vous suivez pour ce faire sont décrites dans Prise d’un projet C++/WinRT et ajout de la prise en charge de C++/CX. Vous pouvez ensuite commencer à déplacer votre code source vers celui-ci et à porter certains du code source C++/CX vers C++/WinRT comme vous le faites.

Dans les deux cas, vous allez interagir (de deux façons) entre votre code C++/WinRT et tout code C++/CX que vous n’avez pas encore porté.

Note

C++/CX et le SDK Windows déclarent des types dans l’espace de noms racine Windows. Un type Windows projeté en C++/WinRT a le même nom complet que le type Windows, mais il est placé dans l'espace de noms winrt C++. Ces espaces de noms distincts vous permettent de porter de C++/CX vers C++/WinRT à votre rythme.

Portage progressif d’un projet XAML

Important

Pour un projet qui utilise XAML, à tout moment, tous vos types de pages XAML doivent être entièrement C++/CX ou entièrement C++/WinRT. Vous pouvez toujours combiner C++/CX et C++/WinRT en dehors des types de pages XAML dans le même projet (dans vos modèles et modèles d’affichage, et ailleurs).

Pour ce scénario, le flux de travail que nous vous recommandons est de créer un projet C++/WinRT et de copier le code source et le balisage à partir du projet C++/CX. Tant que tous vos types de pages XAML sont C++/WinRT, vous pouvez ajouter de nouvelles pages XAML avec Project>Add New Item...>Visual C++>Page vide (C++/WinRT).

Vous pouvez également utiliser un composant Windows Runtime (WRC) pour factoriser le code du projet XAML C++/CX au fur et à mesure que vous le portez.

  • Vous pouvez créer un projet WRC C++/CX, déplacer autant de code C++/CX que possible dans ce projet, puis remplacer le projet XAML par C++/WinRT.
  • Vous pouvez également créer un projet WRC C++/WinRT, laisser le projet XAML en C++/CX, et commencer à migrer le code de C++/CX vers C++/WinRT, puis à déplacer le code obtenu hors du projet XAML et vers le projet de composant.
  • Vous pouvez également avoir un projet de composant C++/CX en même temps qu’un projet de composant C++/WinRT au sein de la même solution, les référencer à partir de votre projet d’application et porter progressivement l’un vers l’autre. Là encore, consultez Interop entre C++/WinRT et C++/CX pour plus d’informations sur l’utilisation des deux projections de langage dans le même projet.

Premières étapes du portage d’un projet C++/CX vers C++/WinRT

Quelle que soit votre stratégie de portage (portage en une seule passe ou portage progressif), votre première étape consiste à préparer votre projet pour le portage. Voici un récapitulatif de ce que nous avons décrit dans Stratégies de portage en termes de type de projet que vous allez commencer et comment le configurer.

  • Portage en une seule passe. Créez un projet dans Visual Studio à l’aide de l’un des modèles de projet C++/WinRT. Déplacez les fichiers de votre projet C++/CX vers ce nouveau projet et portez le code source C++/CX.
  • Portage progressif d’un projet non XAML. Vous pouvez choisir d’ajouter la prise en charge C++/WinRT à votre projet C++/CX (voir Prise d’un projet C++/CX et ajout de la prise en charge C++/WinRT) et du port progressivement. Vous pouvez également choisir de créer un projet C++/WinRT et d’ajouter la prise en charge C++/CX à ce projet (voir Prise d’un projet C++/WinRT et ajout de la prise en charge C++/CX), déplacer des fichiers et déplacer progressivement les fichiers.
  • Portage progressif d’un projet XAML. Créez un nouveau projet C++/WinRT, déplacez les fichiers et effectuez le portage progressivement. À tout moment, vos types de pages XAML doivent être tous C++/WinRT ou C++/CX.

Le reste de cette rubrique s’applique quelle que soit la stratégie de portage que vous choisissez. Il contient un catalogue de détails techniques impliqués dans le portage du code source de C++/CX vers C++/WinRT. Si vous effectuez un portage progressif, vous voudrez probablement également voir Interop entre C++/WinRT et C++/CXet Asynchrony, et l’interopérabilité entre C++/WinRT et C++/CX.

Conventions d’affectation des noms de fichiers

Fichiers de balisage XAML

Origine du fichier C++/CX C++/WinRT
Fichiers XAML du développeur MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (voir ci-dessous)
Fichiers XAML générés MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

Notez que C++/WinRT supprime le .xaml des noms de fichiers *.h et *.cpp.

C++/WinRT ajoute un fichier développeur supplémentaire, le fichier Midl (.idl). C++/CX génère automatiquement ce fichier en interne, en lui ajoutant chaque membre public et protégé. Dans C++/WinRT, vous ajoutez et créez le fichier vous-même. Pour plus d’informations, des exemples de code et une procédure pas à pas de création d’IDL, consultez les contrôles XAML ; liaison à une propriété C++/WinRT.

Consultez également Déplacer les classes d’exécution vers des fichiers MIDL (.idl)

Classes d’exécution

C++/CX n’impose pas de restrictions sur les noms de vos fichiers d’en-tête ; il est courant de placer plusieurs définitions de classes runtime dans un seul fichier d’en-tête, en particulier pour les petites classes. Toutefois, C++/WinRT exige que chaque classe runtime possède son propre fichier d’en-tête nommé après le nom de la classe.

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

Moins courant (mais toujours légal) dans C++/CX consiste à utiliser des fichiers d’en-tête nommés différemment pour les contrôles personnalisés XAML. Vous devez renommer ce fichier d’en-tête pour qu’il corresponde au nom de la classe.

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace implements {
  struct LongNameForA { ... };
}

Configuration requise pour le fichier d’en-tête

C++/CX ne vous oblige pas à inclure de fichiers d’en-tête spéciaux, car il génère automatiquement automatiquement des fichiers d’en-tête à partir de .winmd fichiers. Il est courant, en C++/CX, d’utiliser des directives using pour les espaces de noms auxquels vous faites référence par leur nom.

using namespace Windows::Media::Playback;

String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
    return item->VideoTracks->GetAt(0)->Name;
}

La using namespace Windows::Media::Playback directive nous permet d’écrire MediaPlaybackItem sans préfixe d’espace de noms. Nous avons également modifié l’espace de noms Windows.Media.Core, car item->VideoTracks->GetAt(0) renvoie un Windows.Media.Core.VideoTrack. Mais nous n'avons pas dû taper le nom VideoTrack n'importe où, donc nous n'avons pas besoin d'une using Windows.Media.Core directive.

Toutefois, C++/WinRT vous oblige à inclure un fichier d’en-tête correspondant à chaque espace de noms que vous consommez, même si vous ne le nommez pas.

#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!

using namespace winrt;
using namespace Windows::Media::Playback;

winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
    return item.VideoTracks().GetAt(0).Name();
}

En revanche, même si l’événement MediaPlaybackItem.AudioTracksChanged est de type TypedEventHandler<MediaPlaybackItem, Windows. Foundation.Collections.IVectorChangedEventArgs>, nous n'avons pas besoin d'inclure winrt/Windows.Foundation.Collections.h car nous n'avons pas utilisé cet événement.

C++/WinRT vous oblige également à inclure des fichiers d’en-tête pour les espaces de noms consommés par le balisage XAML.

<!-- MainPage.xaml -->
<Rectangle Height="400"/>

L’utilisation de la classe Rectangle signifie que vous devez ajouter cet élément.

// MainPage.h
#include <winrt/Microsoft.UI.Xaml.Shapes.h>

Si vous oubliez un fichier d’en-tête, tout se compilera correctement, mais vous obtiendrez des erreurs de l’éditeur de liens, parce que les classes consume_ sont absentes.

Passage de paramètres

Lorsque vous écrivez du code source C++/CX, vous passez les types C++/CX en tant que paramètres de fonction en tant que références hat (^).

void LogPresenceRecord(PresenceRecord^ record);

Dans C++/WinRT, pour les fonctions synchrones, vous devez utiliser const& des paramètres par défaut. Cela évitera les copies et le surcoût lié aux verrous. Toutefois, vos coroutines doivent utiliser le passage par valeur pour s’assurer de capturer par valeur et d’éviter les problèmes de durée de vie (pour plus d’informations, consultez Concurrence et opérations asynchrones avec C++/WinRT).

void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);

Un objet C++/WinRT est essentiellement une valeur qui contient un pointeur d’interface sur l’objet Windows Runtime sous-jacent. Lorsque vous copiez un objet C++/WinRT, le compilateur copie le pointeur d’interface encapsulé, incrémentant son nombre de références. La destruction éventuelle de la copie implique de décrémenter le nombre de références. N’encourez donc le surcoût d’une copie que lorsque cela est nécessaire.

Références de variables et de champs

Lorsque vous écrivez du code source C++/CX, vous utilisez des variables avec accent circonflexe (^) pour faire référence à des objets Windows Runtime, et l’opérateur flèche (->) pour déréférencer une variable avec accent circonflexe.

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

Lors du portage vers le code C++/WinRT équivalent, vous pouvez déjà aller loin en supprimant les chapeaux et en remplaçant l’opérateur de flèche (->) par l’opérateur point (.). Les types projetés C++/WinRT sont des valeurs et non des pointeurs.

IVectorView<User> userList = User::Users();

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
    ...

Le constructeur par défaut d’une référence de chapeau C++/CX l’initialise sur null. Voici un exemple de code C++/CX dans lequel nous créons une variable/champ du type correct, mais une variable non initialisée. En d’autres termes, il ne fait pas référence initialement à un TextBlock ; nous avons l’intention d’attribuer une référence ultérieurement.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Pour obtenir l’équivalent en C++/WinRT, consultez Initialisation différée.

Properties

Les extensions de langage C++/CX incluent le concept de propriétés. Lorsque vous écrivez du code source C++/CX, vous pouvez accéder à une propriété comme s’il s’agissait d’un champ. C++ standard n’a pas le concept d’une propriété. Dans C++/WinRT, vous appelez des fonctions get et set.

Dans les exemples qui suivent, XboxUserId, UserState, PresenceDeviceRecords et Size sont toutes les propriétés.

Récupération d’une valeur à partir d’une propriété

Voici comment obtenir une valeur de propriété en C++/CX.

void Sample::LogPresenceRecord(PresenceRecord^ record)
{
    auto id = record->XboxUserId;
    auto state = record->UserState;
    auto size = record->PresenceDeviceRecords->Size;
}

Le code source C++/WinRT équivalent appelle une fonction portant le même nom que la propriété, mais sans paramètres.

void Sample::LogPresenceRecord(PresenceRecord const& record)
{
    auto id = record.XboxUserId();
    auto state = record.UserState();
    auto size = record.PresenceDeviceRecords().Size();
}

Notez que la fonction PresenceDeviceRecords retourne un objet Windows Runtime qui a lui-même une fonction Size. Comme l’objet retourné est également un type projeté C++/WinRT, nous déréférons à l’aide de l’opérateur dot pour appeler Size.

Définition d’une propriété à une nouvelle valeur

La définition d’une propriété sur une nouvelle valeur suit un modèle similaire. Tout d’abord, en C++/CX.

record->UserState = newValue;

Pour effectuer l’équivalent en C++/WinRT, vous appelez une fonction portant le même nom que la propriété et transmettez un argument.

record.UserState(newValue);

Création d’une instance d’une classe

Vous travaillez avec un objet C++/CX par le biais d’un handle, communément appelé référence de chapeau (^). Vous créez un objet via le ref new mot clé, qui appelle à son tour RoActivateInstance pour activer une nouvelle instance de la classe runtime.

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

Un objet C++/WinRT est une valeur ; vous pouvez donc l’allouer sur la pile ou en tant que champ d’un objet. Vous n’utilisezref new jamais (ni new) pour allouer un objet C++/WinRT. En arrière-plan, RoActivateInstance est toujours appelé.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
private:
    Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};

Si une ressource est coûteuse à initialiser, il est courant de retarder l’initialisation jusqu’à ce qu’elle soit réellement nécessaire. Comme indiqué précédemment, le constructeur par défaut d’une référence hat C++/CX l’initialise à null.

using namespace Windows::Storage::Streams;

class Sample
{
public:
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer^ m_gamerPicBuffer;
};

Le même code porté vers C++/WinRT. Notez l’utilisation du constructeur std ::nullptr_t . Pour plus d’informations sur ce constructeur, consultez Initialisation différée.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

Comment le constructeur par défaut affecte les collections

Les types de collection C++ utilisent le constructeur par défaut, ce qui peut entraîner une construction d’objet inattendue.

Scénario C++/CX C++/WinRT (incorrect) C++/WinRT (correct)
Variable locale, initialement vide TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
Variable membre, initialement vide class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
Variable globale, initialement vide TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
Vecteur de références vides std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
Définir une valeur dans une carte std::map<int, TextBox^> boxes;
boxes[2] = value;
std::map<int, TextBox> boxes;
// Creates a TextBox at 2,
// then overwrites it!
boxes[2] = value;
std::map<int, TextBox> boxes;
boxes.insert_or_assign(2, value);
Tableau de références vides TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Paire std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

En savoir plus sur les collections de références vides

Chaque fois que vous disposez d’une plateforme ::Array^ (voir Port Platform ::Array^) en C++/CX, vous avez le choix de le porter vers un std ::vector en C++/WinRT (en fait, n’importe quel conteneur contigu) plutôt que de le laisser comme tableau. Il existe des avantages pour choisir std ::vector.

Par exemple, s’il existe un raccourci pour créer un vecteur de références vides de taille fixe (voir le tableau ci-dessus), il n’existe aucun raccourci pour créer un tableau de références vides. Vous devez répéter nullptr pour chaque élément d’un tableau. Si vous avez trop peu, les extras seront construits par défaut.

Pour un vecteur, vous pouvez le remplir avec des références vides lors de l’initialisation (comme dans le tableau ci-dessus), ou vous pouvez le remplir avec des références vides après l’initialisation avec du code tel que celui-ci.

std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.

En savoir plus sur l’exemple std ::map

L’opérateur [] de std::map se comporte comme suit.

  • Si la clé est trouvée dans la carte, retournez une référence à la valeur existante (que vous pouvez remplacer).
  • Si la clé est introuvable dans la carte, créez une entrée dans la carte composée de la clé (déplacée, si mobile) et d’une valeur construite par défaut, puis retournez une référence à la valeur (que vous pouvez remplacer).

En d’autres termes, l’opérateur [] crée toujours une entrée dans la carte. Ceci est différent de C#, Java et JavaScript.

Conversion d’une classe runtime de base en une classe dérivée

Il est courant d’avoir une référence à une classe de base dont on sait qu’elle fait référence à un objet d’un type dérivé. Dans C++/CX, vous utilisez dynamic_cast pour convertir la référence à la base en référence à dérivée. Le dynamic_cast n’est en réalité qu’un appel caché à QueryInterface. Voici un exemple classique : vous gérez un événement de modification de propriété de dépendance et vous souhaitez effectuer un cast de DependencyObject vers le type réel propriétaire de la propriété de dépendance.

void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject^ d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
    BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };

    if (theControl != nullptr)
    {
        // succeeded ...
    }
}

Le code C++/WinRT équivalent remplace l’appel dynamic_cast à la fonction IUnknown ::try_as , qui encapsule QueryInterface. Vous pouvez également appeler IUnknown::as, ce qui lève une exception si la requête de l’interface requise (l’interface par défaut du type que vous demandez) n’aboutit pas. Voici un exemple de code C++/WinRT.

void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // succeeded ...
    }

    try
    {
        BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
        // succeeded ...
    }
    catch (winrt::hresult_no_interface const&)
    {
        // failed ...
    }
}

Classes dérivées

Pour dériver d’une classe runtime, la classe de base doit être composable. C++/CX ne nécessite aucune mesure particulière pour rendre vos classes composables, mais C++/WinRT l’exige. Vous utilisez le mot clé non scellé pour indiquer que votre classe doit être utilisable en tant que classe de base.

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

Dans votre classe d’en-tête d’implémentation, vous devez inclure le fichier d’en-tête de classe de base avant d’inclure l’en-tête généré automatiquement pour la classe dérivée. Sinon, vous obtiendrez des erreurs telles que « Utilisation illégale de ce type en tant qu’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>
    {
        ...
    }
}

Gestion des événements avec un délégué

Voici un exemple classique de gestion d’un événement en C++/CX, à l’aide d’une fonction lambda en tant que délégué dans ce cas.

auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Il s’agit de l’équivalent en C++/WinRT.

auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Au lieu d’une fonction lambda, vous pouvez choisir d’implémenter votre délégué en tant que fonction libre ou en tant que pointeur vers une fonction membre. Pour plus d’informations, consultez Gérer les événements à l’aide de délégués en C++/WinRT.

Si vous effectuez un portage à partir d’une base de code C++/CX où les événements et les délégués sont utilisés en interne (pas entre les fichiers binaires), winrt ::d elegate vous aidera à répliquer ce modèle en C++/WinRT. Consultez également les délégués paramétrables, les signaux simples et les rappels au sein d’un projet.

Révocation d’un délégué

En C++/CX, vous utilisez l’opérateur -= pour révoquer une inscription d’événement antérieure.

myButton->Click -= token;

Il s’agit de l’équivalent en C++/WinRT.

myButton().Click(token);

Pour plus d’informations et d’options, consultez Révoquer un délégué inscrit.

Boxing et unboxing

C++/CX place automatiquement les scalaires en objets. C++/WinRT vous oblige à appeler explicitement la fonction winrt ::box_value . Les deux langages exigent un déballage explicite. Consultez Boxing et unboxing avec C++/WinRT.

Dans les tables qui suivent, nous allons utiliser ces définitions.

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
Operation C++/CX C++/WinRT
Boxe o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Déballage i = (int)o;
s = (String^)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX et C# déclenchent des exceptions si vous essayez de dissocier un pointeur Null vers un type valeur. C++/WinRT considère cette erreur de programmation et se bloque. Dans C++/WinRT, utilisez la fonction winrt ::unbox_value_or si vous souhaitez gérer le cas où l’objet n’est pas du type que vous pensiez.

Scénario C++/CX C++/WinRT
Déboxer un entier connu i = (int)o; i = unbox_value<int>(o);
Si o a la valeur Null Platform::NullReferenceException Krach
Si o n’est pas un int encapsulé Platform::InvalidCastException Krach
Déballer l’entier, utiliser la valeur de repli si null ; planter dans tous les autres cas i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Déballer un int si possible ; utiliser une solution de repli dans tous les autres cas auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Boxing et unboxing d’une chaîne

Une chaîne est d’une certaine manière un type valeur et d’autres façons un type référence. C++/CX et C++/WinRT traitent les chaînes différemment.

Le type ABI HSTRING est un pointeur vers une chaîne comptée par référence. Mais il ne dérive pas d’IInspectable, donc il n’est pas techniquement un objet. En outre, un HSTRING null représente la chaîne vide. L’encapsulage des éléments qui ne dérivent pas de IInspectable s’effectue en les enveloppant dans un IReference<T>, et Windows Runtime fournit une implémentation standard sous la forme de l’objet PropertyValue (les types personnalisés sont signalés comme PropertyType::OtherType).

C++/CX représente une chaîne Windows Runtime en tant que type référence ; tandis que C++/WinRT projette une chaîne en tant que type valeur. Cela signifie qu’une chaîne null boxée peut avoir différentes représentations selon la façon dont vous y êtes arrivé.

En outre, C++/CX vous permet de déréférer une chaîne null^, auquel cas elle se comporte comme la chaîne "".

Behavior C++/CX C++/WinRT
Déclarations Object^ o;
String^ s;
IInspectable o;
hstring s;
Catégorie de type chaîne Type de référence Type de valeur
null HSTRING est projeté comme (String^)nullptr hstring{}
null et "" sont-ils identiques ? Yes Yes
Validité de null s = nullptr;
s->Length == 0 (valide)
s = hstring{};
s.size() == 0 (valide)
Si vous attribuez une chaîne Null à un objet o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
Si vous attribuez "" à l’objet o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr

Boxing de base et unboxing.

Operation C++/CX C++/WinRT
Encadrer une chaîne o = s;
La chaîne vide devient nullptr.
o = box_value(s);
La chaîne vide devient un objet non null.
Extraire une chaîne connue s = (String^)o;
L’objet Null devient une chaîne vide.
InvalidCastException si ce n’est pas une chaîne.
s = unbox_value<hstring>(o);
L’objet Null se bloque.
Plante si ce n’est pas une chaîne.
Déballer une chaîne facultative s = dynamic_cast<String^>(o);
Un objet null ou une valeur non textuelle devient une chaîne vide.
s = unbox_value_or<hstring>(o, fallback);
Une valeur nulle ou non textuelle devient la valeur de repli.
Chaîne vide conservée.

Concurrence et opérations asynchrones

La Bibliothèque de modèles parallèles (PPL) (concurrency::task, par exemple) a été mise à jour afin de prendre en charge les *hat references* de C++/CX.

Pour C++/WinRT, vous devez utiliser les coroutines et co_await à la place. Pour plus d’informations et des exemples de code, consultez Accès concurrentiel et opérations asynchrones avec C++/WinRT.

Consommation d’objets à partir du balisage XAML

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

En outre, la liaison à un booléen affiche true ou false en C++/CX, mais elle affiche Windows. Foundation.IReference'1<Boolean> en C++/WinRT.

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

Correspondance des types C++/CX Platform avec les types C++/WinRT

C++/CX fournit plusieurs types de données dans l’espace de noms Platform . Ces types ne sont pas standard C++. Vous pouvez donc les utiliser uniquement lorsque vous activez Windows Runtime extensions de langage (Visual Studio propriété de projet C/C++>Général>Consommer Windows Runtime Extension>Oui (/ZW)). Le tableau ci-dessous vous aide à transférer des types de plateforme vers leurs équivalents en C++/WinRT. Une fois que vous avez terminé cela, étant donné que C++/WinRT est standard C++, vous pouvez désactiver l’option /ZW .

C++/CX C++/WinRT
Platform ::Agile^ winrt ::agile_ref
Platform ::Array^ Voir Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt ::hresult_invalid_argument
Platform ::Object^ winrt::Windows::Foundation::IInspectable
Platform ::String^ winrt ::hstring

Porter Platform::Agile^ vers winrt::agile_ref

Le type Platform ::Agile^ en C++/CX représente une classe Windows Runtime accessible à partir de n’importe quel thread. L’équivalent C++/WinRT est winrt ::agile_ref.

En C++/CX.

Platform::Agile<Windows::UI::Core::CoreWindow> m_window;

Dans C++/WinRT (WinUI 3 utilise Microsoft ::UI ::Xaml ::Window au lieu de CoreWindow).

winrt::agile_ref<Microsoft::UI::Xaml::Window> m_window;

Port Platform::Array^

Dans les cas où C++/CX vous oblige à utiliser un tableau, C++/WinRT vous permet d’utiliser n’importe quel conteneur contigu. Découvrez comment le constructeur par défaut affecte les collections pour une raison pour laquelle std ::vector est un bon choix.

Par conséquent, chaque fois que vous disposez d’une plateforme ::Array^ en C++/CX, vos options de portage incluent l’utilisation d’une liste d’initialiseurs, d’un std ::array ou d’un std ::vector. Pour plus d’informations et des exemples de code, consultez les listes d’initialiseurs standard et les tableaux et vecteurs Standard.

Migrer Platform::Exception^ vers winrt::hresult_error

Le type Platform ::Exception^ est produit en C++/CX lorsqu’une API Windows Runtime retourne un HRESULT non S_OK. L’équivalent C++/WinRT est winrt ::hresult_error.

Pour porter vers C++/WinRT, modifiez tout le code qui utilise Platform ::Exception^ pour utiliser winrt ::hresult_error.

En C++/CX.

catch (Platform::Exception^ ex)

En C++/WinRT.

catch (winrt::hresult_error const& ex)

C++/WinRT fournit ces classes d’exception.

Type d'exception Classe de base HRESULT
winrt::hresult_error appeler hresult_error ::to_abi
winrt ::hresult_access_denied winrt::hresult_error E_ACCESSDENIED
winrt ::hresult_canceled winrt::hresult_error ERROR_CANCELLED
winrt ::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt ::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE
winrt ::hresult_illegal_delegate_assignment winrt::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT
winrt ::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL
winrt ::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE
winrt ::hresult_invalid_argument winrt::hresult_error E_INVALIDARG
winrt ::hresult_no_interface winrt::hresult_error E_NOINTERFACE
winrt ::hresult_not_implemented winrt::hresult_error E_NOTIMPL
winrt::hresult_out_of_bounds winrt::hresult_error E_BOUNDS
winrt ::hresult_wrong_thread winrt::hresult_error RPC_E_WRONG_THREAD

Notez que chaque classe (via la classe de base hresult_error ) fournit une fonction to_abi , qui retourne le HRESULT de l’erreur et une fonction de message , qui retourne la représentation sous forme de chaîne de ce HRESULT.

Voici un exemple de levée d’une exception en C++/CX.

throw ref new Platform::InvalidArgumentException(L"A valid User is required");

Et l’équivalent en C++/WinRT.

throw winrt::hresult_invalid_argument{ L"A valid User is required" };

Portage de Platform::Object^ vers winrt::Windows::Foundation::IInspectable

Comme tous les types C++/WinRT, winrt ::Windows ::Foundation ::IInspectable est un type valeur. Voici comment initialiser une variable de ce type sur null.

winrt::Windows::Foundation::IInspectable var{ nullptr };

Migrer Platform::String^ vers winrt::hstring

Platform ::String^ équivaut au type ABI Windows Runtime HSTRING. Pour C++/WinRT, l’équivalent est winrt ::hstring. Toutefois, avec C++/WinRT, vous pouvez appeler Windows Runtime API à l’aide de types de chaînes larges de la bibliothèque C++ Standard, tels que std ::wstring et/ou des littéraux de chaîne large. Pour plus d’informations et des exemples de code, consultez Gestion des chaînes en C++/WinRT.

Avec C++/CX, vous pouvez accéder à la propriété Platform ::String ::D ata pour récupérer la chaîne en tant que tableau const wchar_t* de style C (par exemple, pour le transmettre à std ::wcout).

auto var{ titleRecord->TitleName->Data() };

Pour faire de même avec C++/WinRT, vous pouvez utiliser la fonction hstring ::c_str pour obtenir une version de chaîne de style C terminée par null, tout comme vous pouvez à partir de std ::wstring.

auto var{ titleRecord.TitleName().c_str() };

En ce qui concerne l’implémentation d’API qui prennent ou retournent des chaînes, vous modifiez généralement tout code C++/CX qui utilise Platform ::String^ pour utiliser winrt ::hstring à la place.

Voici un exemple d’API C++/CX qui prend une chaîne.

void LogWrapLine(Platform::String^ str);

Pour C++/WinRT, vous pouvez déclarer cette API dans MIDL 3.0 comme suit.

// LogType.idl
void LogWrapLine(String str);

La chaîne d’outils C++/WinRT génère ensuite le code source pour vous qui ressemble à ceci.

void LogWrapLine(winrt::hstring const& str);

ToString()

Les types C++/CX fournissent la méthode Object ::ToString .

int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".

C++/WinRT ne fournit pas directement cette fonctionnalité, mais vous pouvez recourir à des solutions de remplacement.

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 un nombre limité de types. Vous devrez ajouter des surcharges pour tous les types supplémentaires que vous souhaitez convertir en chaîne.

Language Stringify int Convertir l’énumération en chaîne
C++/CX String^ result = "hello, " + intValue.ToString(); String^ result = "status: " + status.ToString();
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 énumération en chaîne, vous devrez 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 représentations sous forme de 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 appliquent winrt::to_hstring à la propriété liée. Dans le cas du deuxième exemple ( StatusEnum), vous devez fournir votre propre surcharge de winrt ::to_hstring, sinon vous obtiendrez une erreur du compilateur.

Construction de chaînes

C++/CX et C++/WinRT s’appuient sur le std::wstringstream standard pour la construction de chaînes.

Operation C++/CX C++/WinRT
Ajouter une chaîne de caractères, en conservant les valeurs nulles stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Ajouter une chaîne, s’arrêter à la première valeur nulle stream << s->Data(); stream << s.c_str();
Extraire le résultat ws = stream.str(); ws = stream.str();

Autres exemples

Dans les exemples ci-dessous, ws est une variable de type std ::wstring. En outre, alors que C++/CX peut construire une chaîne Platform ::String à partir d’une chaîne 8 bits, C++/WinRT ne le fait pas.

Operation C++/CX C++/WinRT
Créer une chaîne à partir d’un littéral String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Convertir à partir de std ::wstring, en conservant des valeurs Null String^ s = ref new String(ws.c_str(),
  (uint32_t)ws.size());
winrt::hstring s{ ws };
s = winrt::hstring(ws);
// s = ws; // Doesn't compile
Convertir à partir de std ::wstring, arrêter sur la première valeur null String^ s = ref new String(ws.c_str()); winrt::hstring s{ ws.c_str() };
s = winrt::hstring(ws.c_str());
// s = ws.c_str(); // Doesn't compile
Convertir en std ::wstring, en conservant des valeurs Null std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
Convertir en std::wstring, s’arrêter au premier caractère nul std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Passer un littéral à une méthode Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Transmettre std::wstring à la méthode Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

API importantes

Note

De nombreuses rubriques C++/WinRT sont en cours de migration de la documentation UWP vers cette section. Tant que la migration n’est pas terminée, les liens de la liste ci-dessous peuvent conduire à la section des documents UWP. La projection de langage C++/WinRT est la même pour les applications UWP et WinUI 3, afin que le contenu soit applicable dans les deux contextes. Tous les modèles spécifiques à UWP (tels que le cycle de vie des applications ou Windows.UI les API d’espace de noms) sont explicitement notés dans ces articles.