Partager via


Composants Windows Runtime avec C++/CX

Remarque

Cette rubrique existe pour vous aider à gérer votre application C++/CX. Mais nous recommandons que vous utilisiez C++/WinRT pour de nouvelles applications. C++/WinRT est une projection de langage C++17 moderne entièrement standard pour les API Windows Runtime (WinRT), implémentée en tant que bibliothèque basée sur un fichier d’en-tête et conçue pour vous fournir un accès de première classe à l’API Windows moderne. Pour savoir comment créer un composant Windows Runtime avec C++/WinRT, consultez Composants Windows Runtime avec C++/WinRT.

Cette rubrique montre comment utiliser C++/CX pour créer un composant Windows Runtime, un composant pouvant être appelé à partir d’une application Windows universelle créée à l’aide de n’importe quel langage Windows Runtime (C#, Visual Basic, C++ ou JavaScript).

Il existe plusieurs raisons de créer un composant Windows Runtime en C++.

  • Pour tirer parti des performances de C++ dans des opérations complexes ou nécessitant beaucoup de ressources informatiques.
  • Pour réutiliser du code déjà écrit et testé.

Lorsque vous générez une solution qui contient un projet JavaScript ou .NET et un projet de composant Windows Runtime, les fichiers projet JavaScript et la DLL compilée sont fusionnés dans un package, que vous pouvez déboguer localement dans le simulateur ou à distance sur un appareil connecté. Vous pouvez également distribuer uniquement le projet de composant en tant que KIT DE développement logiciel (SDK) d’extension. Pour plus d’informations, consultez Création d’un SDK.

En règle générale, lorsque vous codez votre composant C++/CX, utilisez la bibliothèque C++ standard et les types intégrés, à l’exception de la limite d’interface binaire abstraite (ABI) où vous transmettez des données vers et depuis du code dans un autre package .winmd. Là, utilisez les types Windows Runtime et la syntaxe spéciale prise en charge par C++/CX pour la création et la manipulation de ces types. En outre, dans votre code C++/CX, utilisez des types tels que délégué et événement pour implémenter des événements pouvant être déclenchés à partir de votre composant et gérés en JavaScript, Visual Basic, C++ou C#. Pour plus d’informations sur la syntaxe C++/CX, consultez La référence du langage Visual C++ (C++/CX).

Règles de casse et d’affectation de noms

JavaScript

JavaScript respecte la casse. Par conséquent, vous devez suivre ces conventions de casse :

  • Lorsque vous référencez des espaces de noms et des classes C++, utilisez la même casse que celle utilisée côté C++.
  • Lorsque vous appelez des méthodes, utilisez la casse chameau même si le nom de la méthode est capitalisé côté C++. Par exemple, une méthode C++ GetDate() doit être appelée à partir de JavaScript en tant que getDate().
  • Un nom de classe et un nom d’espace de noms pouvant être activés ne peuvent pas contenir de caractères UNICODE.

.NET

Les langages .NET suivent leurs règles normales de casse.

Instanciation de l’objet

Seuls les types Windows Runtime peuvent être passés à travers la limite ABI. Le compilateur génère une erreur si le composant a un type tel que std ::wstring comme un type de retour ou un paramètre dans une méthode publique. Les extensions de composant Visual C++ (C++/CX) intégrées incluent les scalaires habituels tels que int et double, ainsi que leurs équivalents typedef int32, float64, etc. Pour plus d’informations, consultez Type System (C++/CX).

// ref class definition in C++
public ref class SampleRefClass sealed
{
    // Class members...

    // #include <valarray>
public:
    double LogCalc(double input)
    {
        // Use C++ standard library as usual.
        return std::log(input);
    }

};
//Instantiation in JavaScript (requires "Add reference > Project reference")
var nativeObject = new CppComponent.SampleRefClass();
//Call a method and display result in a XAML TextBlock
var num = nativeObject.LogCalc(21.5);
ResultText.Text = num.ToString();

Types intégrés C++/CX, types de bibliothèque et types Windows Runtime

Une classe activable (également appelée classe ref) est une classe qui peut être instanciée à partir d’un autre langage tel que JavaScript, C# ou Visual Basic. Pour être consommable à partir d’un autre langage, un composant doit contenir au moins une classe activable.

Un composant Windows Runtime peut contenir plusieurs classes activables publiques, ainsi que des classes supplémentaires connues uniquement en interne pour le composant. Appliquez l’attribut WebHostHidden aux types C++/CX qui ne sont pas destinés à être visibles par JavaScript.

Toutes les classes publiques doivent résider dans le même espace de noms racine qui a le même nom que le fichier de métadonnées du composant. Par exemple, une classe nommée A.B.C.MyClass peut être instanciée uniquement si elle est définie dans un fichier de métadonnées nommé A.winmd, A.B.winmd ou A.B.C.winmd. Il n'est pas requis que le nom de la DLL corresponde au nom du fichier .winmd.

Le code client crée une instance du composant à l’aide du mot clé new (New in Visual Basic) comme pour n’importe quelle classe.

Une classe activable doit être déclarée en tant que classe ref publique scellée. Le mot clé de classe ref indique au compilateur de créer la classe en tant que type compatible Windows Runtime, et le mot clé scellé spécifie que la classe ne peut pas être héritée. Windows Runtime ne prend actuellement pas en charge un modèle d’héritage généralisé ; un modèle d’héritage limité prend en charge la création de contrôles XAML personnalisés. Pour plus d’informations, consultez les classes ref et les structs (C++/CX).

Pour C++/CX, toutes les primitives numériques sont définies dans l’espace de noms par défaut. L’espace de noms Platform contient des classes C++/CX spécifiques au système de type Windows Runtime. Il s’agit notamment de la classe Platform ::String et de la classe Platform ::Object . Les types de collection concrets tels que Platform ::Collections ::Map et Platform ::Collections ::Vector sont définis dans l’espace de noms Platform ::Collections . Les interfaces publiques que ces types implémentent sont définies dans l’espace de noms Windows ::Foundation ::Collections (C++/CX). Il s’agit de ces types d’interface qui sont consommés par JavaScript, C# et Visual Basic. Pour plus d’informations, consultez Type System (C++/CX).

Méthode qui retourne une valeur de type intégré

    // #include <valarray>
public:
    double LogCalc(double input)
    {
        // Use C++ standard library as usual.
        return std::log(input);
    }
//Call a method
var nativeObject = new CppComponent.SampleRefClass;
var num = nativeObject.logCalc(21.5);
document.getElementById('P2').innerHTML = num;

Méthode qui retourne un struct de valeur personnalisé

namespace CppComponent
{
    // Custom struct
    public value struct PlayerData
    {
        Platform::String^ Name;
        int Number;
        double ScoringAverage;
    };

    public ref class Player sealed
    {
    private:
        PlayerData m_player;
    public:
        property PlayerData PlayerStats
        {
            PlayerData get(){ return m_player; }
            void set(PlayerData data) {m_player = data;}
        }
    };
}

Pour passer des structs de valeur définis par l’utilisateur dans l’ABI, définissez un objet JavaScript qui a les mêmes membres que le struct de valeur défini dans C++/CX. Vous pouvez ensuite passer cet objet en tant qu’argument à une méthode C++/CX afin que l’objet soit implicitement converti en type C++/CX.

// Get and set the value struct
function GetAndSetPlayerData() {
    // Create an object to pass to C++
    var myData =
        { name: "Bob Homer", number: 12, scoringAverage: .357 };
    var nativeObject = new CppComponent.Player();
    nativeObject.playerStats = myData;

    // Retrieve C++ value struct into new JavaScript object
    var myData2 = nativeObject.playerStats;
    document.getElementById('P3').innerHTML = myData.name + " , " + myData.number + " , " + myData.scoringAverage.toPrecision(3);
}

Une autre approche consiste à définir une classe qui implémente IPropertySet (non affiché).

Dans les langages .NET, vous créez simplement une variable du type défini dans le composant C++/CX.

private void GetAndSetPlayerData()
{
    // Create a ref class
    var player = new CppComponent.Player();

    // Create a variable of a value struct
    // type that is defined in C++
    CppComponent.PlayerData myPlayer;
    myPlayer.Name = "Babe Ruth";
    myPlayer.Number = 12;
    myPlayer.ScoringAverage = .398;

    // Set the property
    player.PlayerStats = myPlayer;

    // Get the property and store it in a new variable
    CppComponent.PlayerData myPlayer2 = player.PlayerStats;
    ResultText.Text += myPlayer.Name + " , " + myPlayer.Number.ToString() +
        " , " + myPlayer.ScoringAverage.ToString();
}

Méthodes surchargées

Une classe de référence publique C++/CX peut contenir des méthodes surchargées, mais JavaScript a une capacité limitée à différencier les méthodes surchargées. Par exemple, il peut indiquer la différence entre ces signatures :

public ref class NumberClass sealed
{
public:
    int GetNumber(int i);
    int GetNumber(int i, Platform::String^ str);
    double GetNumber(int i, MyData^ d);
};

Mais il ne peut pas dire la différence entre ces éléments :

int GetNumber(int i);
double GetNumber(double d);

Dans les cas ambigus, vous pouvez vous assurer que JavaScript appelle toujours une surcharge spécifique en appliquant l’attribut Windows ::Foundation ::Metadata ::D efaultOverload à la signature de méthode dans le fichier d’en-tête.

Ce code JavaScript appelle toujours la surcharge attribuée :

var nativeObject = new CppComponent.NumberClass();
var num = nativeObject.getNumber(9);
document.getElementById('P4').innerHTML = num;

.NET

Les langages .NET reconnaissent les surcharges dans une classe ref C++/CX comme dans n’importe quelle classe .NET.

Date/Heure

Dans Windows Runtime, un objet Windows ::Foundation ::D ateTime n’est qu’un entier signé 64 bits qui représente le nombre d’intervalles de 100 nanosecondes avant ou après le 1er janvier 1601. Il n’existe aucune méthode sur un objet Windows :Foundation ::D ateTime. Au lieu de cela, chaque langage projette la DateTime de la façon native de ce langage : l’objet Date en JavaScript et les types System.DateTime et System.DateTimeOffset dans .NET.

public  ref class MyDateClass sealed
{
public:
    property Windows::Foundation::DateTime TimeStamp;
    void SetTime(Windows::Foundation::DateTime dt)
    {
        auto cal = ref new Windows::Globalization::Calendar();
        cal->SetDateTime(dt);
        TimeStamp = cal->GetDateTime(); // or TimeStamp = dt;
    }
};

Lorsque vous passez une valeur DateTime de C++/CX à JavaScript, JavaScript l’accepte en tant qu’objet Date et l’affiche par défaut sous forme de chaîne de date de longue forme.

function SetAndGetDate() {
    var nativeObject = new CppComponent.MyDateClass();

    var myDate = new Date(1956, 4, 21);
    nativeObject.setTime(myDate);

    var myDate2 = nativeObject.timeStamp;

    //prints long form date string
    document.getElementById('P5').innerHTML = myDate2;

}

Lorsqu’un langage .NET passe un System.DateTime à un composant C++/CX, la méthode l’accepte en tant que Windows ::Foundation ::D ateTime. Lorsque le composant transmet un Windows ::Foundation ::D ateTime à une méthode .NET, la méthode Framework l’accepte en tant que DateTimeOffset.

private void DateTimeExample()
{
    // Pass a System.DateTime to a C++ method
    // that takes a Windows::Foundation::DateTime
    DateTime dt = DateTime.Now;
    var nativeObject = new CppComponent.MyDateClass();
    nativeObject.SetTime(dt);

    // Retrieve a Windows::Foundation::DateTime as a
    // System.DateTimeOffset
    DateTimeOffset myDate = nativeObject.TimeStamp;

    // Print the long-form date string
    ResultText.Text += myDate.ToString();
}

Collections et tableaux

Les collections sont toujours passées à travers la limite ABI en tant que handles aux types Windows Runtime tels que Windows ::Foundation ::Collections ::IVector^ et Windows ::Foundation ::Collections ::IMap^. Par exemple, si vous renvoyez un handle à une plateforme ::Collections ::Map, il se convertit implicitement en Windows ::Foundation ::Collections ::IMap^. Les interfaces de collection sont définies dans un espace de noms distinct des classes C++/CX qui fournissent les implémentations concrètes. Les langages JavaScript et .NET consomment les interfaces. Pour plus d’informations, consultez Collections (C++/CX) et Array et WriteOnlyArray (C++/CX).

Passage d’IVector

// Windows::Foundation::Collections::IVector across the ABI.
//#include <algorithm>
//#include <collection.h>
Windows::Foundation::Collections::IVector<int>^ SortVector(Windows::Foundation::Collections::IVector<int>^ vec)
{
    std::sort(begin(vec), end(vec));
    return vec;
}
var nativeObject = new CppComponent.CollectionExample();
// Call the method to sort an integer array
var inVector = [14, 12, 45, 89, 23];
var outVector = nativeObject.sortVector(inVector);
var result = "Sorted vector to array:";
for (var i = 0; i < outVector.length; i++)
{
    outVector[i];
    result += outVector[i].toString() + ",";
}
document.getElementById('P6').innerHTML = result;

Les langages .NET voient IVector<T> comme IList<T>.

private void SortListItems()
{
    IList<int> myList = new List<int>();
    myList.Add(5);
    myList.Add(9);
    myList.Add(17);
    myList.Add(2);

    var nativeObject = new CppComponent.CollectionExample();
    IList<int> mySortedList = nativeObject.SortVector(myList);

    foreach (var item in mySortedList)
    {
        ResultText.Text += " " + item.ToString();
    }
}

Passage d’IMap

// #include <map>
//#include <collection.h>
Windows::Foundation::Collections::IMap<int, Platform::String^> ^GetMap(void)
{    
    Windows::Foundation::Collections::IMap<int, Platform::String^> ^ret =
        ref new Platform::Collections::Map<int, Platform::String^>;
    ret->Insert(1, "One ");
    ret->Insert(2, "Two ");
    ret->Insert(3, "Three ");
    ret->Insert(4, "Four ");
    ret->Insert(5, "Five ");
    return ret;
}
// Call the method to get the map
var outputMap = nativeObject.getMap();
var mStr = "Map result:" + outputMap.lookup(1) + outputMap.lookup(2)
    + outputMap.lookup(3) + outputMap.lookup(4) + outputMap.lookup(5);
document.getElementById('P7').innerHTML = mStr;

Les langages .NET voient IMap et IDictionary<K, V>.

private void GetDictionary()
{
    var nativeObject = new CppComponent.CollectionExample();
    IDictionary<int, string> d = nativeObject.GetMap();
    ResultText.Text += d[2].ToString();
}

Propriétés

Une classe ref publique dans les extensions de composant C++/CX expose les membres de données publiques en tant que propriétés, à l’aide du mot clé de propriété. Le concept est identique aux propriétés .NET. Une propriété triviale ressemble à un membre de données, car sa fonctionnalité est implicite. Une propriété non triviale a des accesseurs get et set explicites et une variable privée nommée qui est le « magasin de stockage » pour la valeur. Dans cet exemple, la variable membre privée _propertyAValue est le magasin de stockage pour PropertyA. Une propriété peut déclencher un événement lorsque sa valeur change, et une application cliente peut s’inscrire pour recevoir cet événement.

//Properties
public delegate void PropertyChangedHandler(Platform::Object^ sender, int arg);
public ref class PropertyExample  sealed
{
public:
    PropertyExample(){}

    // Event that is fired when PropertyA changes
    event PropertyChangedHandler^ PropertyChangedEvent;

    // Property that has custom setter/getter
    property int PropertyA
    {
        int get() { return m_propertyAValue; }
        void set(int propertyAValue)
        {
            if (propertyAValue != m_propertyAValue)
            {
                m_propertyAValue = propertyAValue;
                // Fire event. (See event example below.)
                PropertyChangedEvent(this, propertyAValue);
            }
        }
    }

    // Trivial get/set property that has a compiler-generated backing store.
    property Platform::String^ PropertyB;

private:
    // Backing store for propertyA.
    int m_propertyAValue;
};
var nativeObject = new CppComponent.PropertyExample();
var propValue = nativeObject.propertyA;
document.getElementById('P8').innerHTML = propValue;

//Set the string property
nativeObject.propertyB = "What is the meaning of the universe?";
document.getElementById('P9').innerHTML += nativeObject.propertyB;

Les langages .NET accèdent aux propriétés d’un objet C++/CX natif comme ils le feraient sur un objet .NET.

private void GetAProperty()
{
    // Get the value of the integer property
    // Instantiate the C++ object
    var obj = new CppComponent.PropertyExample();

    // Get an integer property
    var propValue = obj.PropertyA;
    ResultText.Text += propValue.ToString();

    // Set a string property
    obj.PropertyB = " What is the meaning of the universe?";
    ResultText.Text += obj.PropertyB;

}

Délégués et événements

Un délégué est un type Windows Runtime qui représente un objet de fonction. Vous pouvez utiliser des délégués en connexion avec des événements, des rappels et des appels de méthode asynchrone pour spécifier une action à effectuer ultérieurement. Comme un objet de fonction, le délégué fournit une sécurité de type en permettant au compilateur de vérifier le type de retour et les types de paramètres de la fonction. La déclaration d’un délégué ressemble à une signature de fonction, l’implémentation ressemble à une définition de classe et l’appel ressemble à un appel de fonction.

Ajout d’un écouteur d’événements

Vous pouvez utiliser le mot clé d’événement pour déclarer un membre public d’un type délégué spécifié. Le code client s’abonne à l’événement à l’aide des mécanismes standard fournis dans le langage particulier.

public:
    event SomeHandler^ someEvent;

Cet exemple utilise le même code C++ que pour la section des propriétés précédentes.

function Button_Click() {
    var nativeObj = new CppComponent.PropertyExample();
    // Define an event handler method
    var singlecasthandler = function (ev) {
        document.getElementById('P10').innerHTML = "The button was clicked and the value is " + ev;
    };

    // Subscribe to the event
    nativeObj.onpropertychangedevent = singlecasthandler;

    // Set the value of the property and fire the event
    var propValue = 21;
    nativeObj.propertyA = 2 * propValue;

}

Dans les langages .NET, l’abonnement à un événement dans un composant C++ est le même que l’abonnement à un événement dans une classe .NET :

//Subscribe to event and call method that causes it to be fired.
private void TestMethod()
{
    var objWithEvent = new CppComponent.PropertyExample();
    objWithEvent.PropertyChangedEvent += objWithEvent_PropertyChangedEvent;

    objWithEvent.PropertyA = 42;
}

//Event handler method
private void objWithEvent_PropertyChangedEvent(object __param0, int __param1)
{
    ResultText.Text = "the event was fired and the result is " +
         __param1.ToString();
}

Ajout de plusieurs écouteurs d’événements pour un seul événement

JavaScript a une méthode addEventListener qui permet à plusieurs gestionnaires de s’abonner à un seul événement.

public delegate void SomeHandler(Platform::String^ str);

public ref class LangSample sealed
{
public:
    event SomeHandler^ someEvent;
    property Platform::String^ PropertyA;

    // Method that fires an event
    void FireEvent(Platform::String^ str)
    {
        someEvent(Platform::String::Concat(str, PropertyA->ToString()));
    }
    //...
};
// Add two event handlers
var multicast1 = function (ev) {
    document.getElementById('P11').innerHTML = "Handler 1: " + ev.target;
};
var multicast2 = function (ev) {
    document.getElementById('P12').innerHTML = "Handler 2: " + ev.target;
};

var nativeObject = new CppComponent.LangSample();
//Subscribe to the same event
nativeObject.addEventListener("someevent", multicast1);
nativeObject.addEventListener("someevent", multicast2);

nativeObject.propertyA = "42";

// This method should fire an event
nativeObject.fireEvent("The answer is ");

En C#, n’importe quel nombre de gestionnaires d’événements peuvent s’abonner à l’événement à l’aide de l’opérateur += comme indiqué dans l’exemple précédent.

Énumérations

Une énumération Windows Runtime en C++/CX est déclarée à l’aide de l’énumération de classe publique ; elle ressemble à une énumération délimitée en C++standard.

public enum class Direction {North, South, East, West};

public ref class EnumExampleClass sealed
{
public:
    property Direction CurrentDirection
    {
        Direction  get(){return m_direction; }
    }

private:
    Direction m_direction;
};

Les valeurs d’énumération sont passées entre C++/CX et JavaScript en tant qu’entiers. Vous pouvez éventuellement déclarer un objet JavaScript qui contient les mêmes valeurs nommées que l’énumération C++/CX et l’utiliser comme suit.

var Direction = { 0: "North", 1: "South", 2: "East", 3: "West" };
//. . .

var nativeObject = new CppComponent.EnumExampleClass();
var curDirection = nativeObject.currentDirection;
document.getElementById('P13').innerHTML =
Direction[curDirection];

C# et Visual Basic prennent en charge les énumérations. Ces langages voient une classe d’énumération publique C++ tout comme elles verraient une énumération .NET.

Méthodes asynchrones

Pour utiliser des méthodes asynchrones exposées par d’autres objets Windows Runtime, utilisez la classe de tâches (runtime d’accès concurrentiel) . Pour plus d’informations, consultez et parallélisme des tâches (runtime d’accès concurrentiel).

Pour implémenter des méthodes asynchrones dans C++/CX, utilisez la fonction create_async définie dans ppltasks.h. Pour plus d’informations, consultez Création d’opérations asynchrones dans C++/CX pour les applications UWP. Pour obtenir un exemple, consultez la procédure pas à pas de création d’un composant Windows Runtime C++/CX et l’appel à partir de JavaScript ou C#. Les langages .NET consomment des méthodes asynchrones C++/CX comme elles le feraient pour toute méthode asynchrone définie dans .NET.

Exceptions

Vous pouvez lever n’importe quel type d’exception défini par Windows Runtime. Vous ne pouvez pas dériver des types personnalisés à partir de n’importe quel type d’exception Windows Runtime. Toutefois, vous pouvez lever COMException et fournir un HRESULT personnalisé accessible par le code qui intercepte l’exception. Il n’existe aucun moyen de spécifier un message personnalisé dans une exception COMException.

Conseils de débogage

Lorsque vous déboguez une solution JavaScript qui a une DLL de composant, vous pouvez définir le débogueur pour activer l’exécution pas à pas dans le script ou le passage pas à pas dans le code natif dans le composant, mais pas les deux en même temps. Pour modifier le paramètre, sélectionnez le nœud de projet JavaScript dans Explorateur de solutions, puis choisissez Propriétés, Débogage, Type de débogueur.

Veillez à sélectionner les fonctionnalités appropriées dans le concepteur de packages. Par exemple, si vous tentez d’ouvrir un fichier image dans la bibliothèque Images de l’utilisateur à l’aide des API Windows Runtime, veillez à cocher la case à cocher Bibliothèque d’images dans le volet Fonctionnalités du concepteur de manifeste.

Si votre code JavaScript ne semble pas reconnaître les propriétés publiques ou les méthodes du composant, assurez-vous que, dans JavaScript, vous utilisez la casse chameau. Par exemple, la méthode LogCalc C++/CX doit être référencée en tant que logCalc en JavaScript.

Si vous supprimez un projet de composant Windows Runtime C++/CX d’une solution, vous devez également supprimer manuellement la référence du projet JavaScript. L’échec de cette opération empêche les opérations de débogage ou de génération suivantes. Si nécessaire, vous pouvez ensuite ajouter une référence d’assembly à la DLL.