Partager via


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

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 dans C++/WinRT.

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

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 bibliothèque de modèles parallèles (PPL) tâches vers des coroutines. Les modèles sont différents. Il n’existe pas de correspondance directe naturelle des tâches PPL aux coroutines, et il n’existe aucun moyen simple de transposer automatiquement le code qui fonctionne dans 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.

Portage en une seule passe

Si vous êtes en mesure de porter l'intégralité de votre projet en une seule étape, vous n'aurez besoin que de cette rubrique pour les informations requises (et vous n'aurez pas besoin des rubriques d'interopérabilité et 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 prise en charge de Visual Studio pour 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 Prendre un projet C++/CX et ajouter le support 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.

Remarque

Si vous avez un projet de composant Windows Runtime, le portage en 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 des projets de composants Windows Runtime, 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 ainsi que Asynchronie et Interop 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 Prendre un projet C++/CX et ajouter le support C++/WinRT. Vous pouvez ensuite porter progressivement à partir de ce point.

Une autre option consiste à créer un projet dans Visual Studio à l’aide de l’un des modèles de projet C++/WinRT (consultez prise en charge de Visual Studio pour 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 Prendre un projet C++/WinRT et y ajouter la prise en charge C++/CX. Vous pouvez ensuite commencer à déplacer votre code source vers celui-ci, et le portage certaines 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é.

Remarque

À la fois 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 C++ winrt. 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 de de types de pages XAML au sein du même projet (dans vos modèles et viewmodels, etc.).

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>Ajouter un nouvel élément...>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 nouveau projet WRC en C++/WinRT, laisser le projet XAML en C++/CX, commencer à porter C++/CX vers C++/WinRT et déplacer le code résultant hors du projet XAML pour l'intégrer dans 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. De nouveau, consultez l'Interopérabilité entre C++/WinRT et C++/CX aux références et pour plus de détails sur l'utilisation des deux projections de langage dans un projet commun.

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ésumé de ce que nous avons décrit dans Stratégies de portage de 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 d'un projet non-XAML progressivement. Vous pouvez choisir d’ajouter la prise en charge C++/WinRT à votre projet C++/CX (consultez Prise d’un projet C++/CX et ajout de la prise en charge de C++/WinRT), et le port progressivement. Vous pouvez également choisir de créer un projet C++/WinRT et d’ajouter la prise en charge C++/CX à ce projet (consultez 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.
  • Porter un projet XAML progressivement. Créez un nouveau projet C++/WinRT, déplacez les fichiers et faites la migration progressivement. À tout moment, vos types de pages XAML doivent être tous les C++/WinRT ou tous les 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 souhaiterez probablement également consulter Interop entre C++/WinRT et C++/CX ainsi que Asynchronie, etInterop entre C++/WinRT et C++/CX.

Conventions d’affectation de noms de fichiers

Fichiers de balisage XAML

Origine du fichier C++/CX C++/WinRT
fichiers XAML 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 les .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 contrôles XAML ; liez à une propriété C++/WinRT.

Consultez également Factoring des classes runtime dans les fichiers Midl (.idl)

Classes de runtime

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 fichiers .winmd. Il est courant dans C++/CX d’utiliser des directives using pour les espaces de noms que vous utilisez par leur nom.

using namespace Windows::Media::Playback;

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

La directive using namespace Windows::Media::Playback 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) retourne 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 directive using Windows.Media.Core.

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/Windows.UI.Xaml.Shapes.h>

Si vous oubliez un fichier d’en-tête, tout va se compiler, mais vous obtiendrez des erreurs de l'éditeur de liens, car les classes consume_ ne sont pas présentes.

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 sous forme de références chapeau (^).

void LogPresenceRecord(PresenceRecord^ record);

Dans C++/WinRT, pour les fonctions synchrones, vous devez utiliser const& paramètres par défaut. Cela évitera les copies et les surcharges interblocées. Toutefois, vos coroutines doivent utiliser le passage par valeur pour s’assurer qu’elles capturent par valeur et évitent les problèmes de durée de vie (pour plus d’informations, consultez la concurrence et les opérations asynchrones avec C++/WinRT).

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

Un objet C++/WinRT est fondamentalement une valeur qui contient un pointeur d’interface vers 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. Par conséquent, n'encourez la surcharge 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 chapeau (^) pour référencer des objets Windows Runtime et l’opérateur de flèche (->) pour déréférencer une variable dotée d'un chapeau.

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 ferez déjà un grand pas en supprimant les pointeurs chapeautés 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 l'équivalent en C++/WinRT, référez-vous à initialisation différée.

Propriétés

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 suivants, XboxUserId, UserState, PresenceDeviceRecordset 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, également connu sous le nom de référence chapeau (^). Vous créez un objet via le mot clé ref new, 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'jamais utiliser ref new (ni new) pour allouer un objet C++/WinRT. En coulisses, 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 mentionné 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 Platform ::Array^ (voir Port Platform ::Array^) en C++/CX, vous avez le choix de le convertir en un std ::vector en C++/WinRT (en fait, n’importe quel conteneur contigu) plutôt que de le conserver 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 subscript [] pour 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 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 vers la base dont vous savez qu'elle fait référence à un objet d’un type dérivé. En C++/CX, vous utilisez dynamic_cast pour cast la référence à la base en référence à dérivée. Le dynamic_cast n’est vraiment qu’un appel masqué à 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 au type réel propriétaire de la propriété de dépendance.

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

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

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

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::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 d'exécution, la classe de base doit être composable . C++/CX n'exige pas de démarche particulière pour composer vos classes, mais C++/WinRT le fait. Vous utilisez le mot clé non scellé pour indiquer que votre classe doit être utilisable en tant que classe de base.

unsealed runtimeclass BasePage : Windows.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 délégués paramétrables, signaux simples et fonctions de rappel au sein d'un projet.

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

Dans 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 undélégué inscrit.

Boxing et unboxing (encapsulation et désencapsulation)

C++/CX place automatiquement les scalaires en objets. C++/WinRT vous oblige à appeler explicitement la fonction winrt ::box_value. Les deux langues exigent que vous déballiez explicitement. 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;
Opération 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 déboîter un pointeur nul en 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 avez pensé.

Scénario C++/CX C++/WinRT
Extraire 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 entier encapsulé Platform::InvalidCastException Krach
Extraire l'entier, utiliser une valeur de secours si null ; provoquer un plantage si autre chose i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Déballer l'entier si possible ; utiliser une solution de repli pour toute autre chose auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

L'encapsulation et la désencapsulation 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 de IInspectable , donc il n’est pas techniquement un objet . En outre, un HSTRING null représente la chaîne vide. L'encapsulation des éléments non dérivés de IInspectable est réalisée 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 de 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érencer une String^ null^, auquel cas il se comporte comme la chaîne "".

Comportement C++/CX C++/WinRT
Déclarations Object^ o;
String^ s;
IInspectable o;
hstring s;
Catégorie de type chaîne de caractères Type de référence Type de valeur
null HSTRING projets en (String^)nullptr hstring{}
Est-ce que null et "" identiques ? Oui Oui
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.

Opération 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.
Déballer une chaîne connue s = (String^)o;
L’objet Null devient une chaîne vide.
InvalidCastException s’il ne s’agit pas d’une chaîne de caractères.
s = unbox_value<hstring>(o);
L’objet Null se bloque.
Se bloque si ce n’est pas une chaîne.
Déballer une chaîne possible s = dynamic_cast<String^>(o);
L’objet Null ou la chaîne non-chaîne devient une chaîne vide.
s = unbox_value_or<hstring>(o, fallback);
Null ou non-chaîne est utilisé comme solution 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 pour prendre en charge les références de type chapeau en C++/CX.

Pour C++/WinRT, vous devez utiliser les coroutines et les co_await à la place. Pour plus d’informations et pour obtenir des exemples de code, consultez opérations simultanées et asynchrones avec C++/WinRT.

Consommer des 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<booléen> en C++/WinRT.

Pour plus d’informations et des exemples de code, consultez Consommation d'objets depuis le balisage.

Mappage des types de de plateforme C++/CX aux 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 les extensions de langage Windows Runtime (propriété de projet Visual Studio C/C++>Général>Consommer l’extension Windows Runtime>Oui (/ZW)). Le tableau ci-dessous vous aide à transférer des types de Platform 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^ Consultez port plateforme ::Array^
Platform ::Exception^ winrt ::hresult_error
Platform ::InvalidArgumentException^ winrt ::hresult_invalid_argument
Platform ::Object^ winrt ::Windows ::Foundation ::IInspectable
Platform ::String^ winrt ::hstring

Port 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;

En C++/WinRT.

winrt::agile_ref<Windows::UI::Core::CoreWindow> 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. Consultez Comment le constructeur par défaut affecte les collections pour une raison pour laquelle std ::vector est un bon choix.

Ainsi, chaque fois que vous avez une Platform ::Array^ en C++/CX, vos options de portage incluent l’utilisation d’une liste d’initialiseurs, d’un std ::arrayou d’un std ::vector. Pour plus d’informations et des exemples de code, consultez listes d’initialiseurs standard et tableaux et vecteurs standard.

Port 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 passer à 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 ERREUR_ANNULÉE
winrt ::hresult_changed_state winrt ::hresult_error E_CHANGED_STATE (État changé)
winrt ::hresult_class_not_available winrt ::hresult_error CLASSE_E_CLASSEINDISPONIBLE
winrt ::hresult_illegal_delegate_assignment winrt ::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT (Erreur d'assignation illégale de délégué)
winrt ::hresult_illegal_method_call winrt ::hresult_error E_APPEL_DE_MÉTHODE_ILLÉGALE (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 hrESULT de l’erreur et une fonction 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" };

Port Platform ::Object^ vers winrt ::Windows ::Foundation ::IInspectable

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

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

Port Platform::String^ vers winrt::hstring

Platform::String^ est équivalent au type d'ABI HSTRING de Windows Runtime. Pour C++/WinRT, l’équivalent est winrt ::hstring. Toutefois, avec C++/WinRT, vous pouvez appeler des API Windows Runtime à 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::Data pour récupérer la chaîne en tant que tableau const wchar_t* (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() };

Quand il s’agit d’implémenter des 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 vous tourner vers des 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 un nombre limité de types. Vous devez ajouter des surcharges pour les types supplémentaires que vous voulez convertir en chaîne.

Langue Convertir un entier en chaîne Conversion en chaîne d'une énumération
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);

Pour la transformation d'une énumération en chaîne de caractères, 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înes de caractères sont souvent consommé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 réaliseront winrt::to_hstring de la propriété liée. Dans le cas du deuxième exemple (StatusEnum), il faut fournir votre propre surcharge de winrt::to_hstring, sinon vous recevrez une erreur de compilation.

Génération de chaînes

C++/CX et C++/WinRT s'appuient sur la norme std::wstringstream pour la génération de chaînes.

Opération C++/CX C++/WinRT
Ajouter une chaîne, conserver les null stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Ajouter une chaîne de caractères, arrêter sur la première valeur null 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 un Platform ::String à partir d’une chaîne 8 bits, C++/WinRT ne le fait pas.

Opération C++/CX C++/WinRT
Construire 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 préservant les 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 depuis std::wstring, s'arrêter sur la première valeur nulle 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 les nuls 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 sur la première valeur null 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");
Passez 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