Componenti Windows Runtime con C++/CX

Nota

Questo argomento è utile per gestire l'applicazione C++/CX. Ma consigliamo di usare C++/WinRT per le nuove applicazioni. C++/WinRT è una proiezione del linguaggio C++ 17 interamente standard e moderna per le API di Windows Runtime (WinRT), implementata come libreria basata su file di intestazione e progettata per fornirti accesso privilegiato alla moderna API di Windows. Per informazioni su come creare un componente Windows Runtime con C++/WinRT, vedere Componenti Windows Runtime con C++/WinRT.

Questo argomento illustra come usare C++/CX per creare un componente Windows Runtime, ossia un componente che può essere chiamato da un'app Universal Windows creata in linguaggio Windows Runtime (C#, Visual Basic, C++ o Javascript).

Esistono diversi motivi per creare un componente Windows Runtime in C++.

  • Ottenere i vantaggi in termini di prestazioni di C++ in operazioni complesse o intensive dal punto di vista del calcolo.
  • Per riutilizzare il codice già scritto e testato.

Quando si compila una soluzione che contiene un progetto JavaScript o .NET e un progetto di componente Windows Runtime, i file di progetto JavaScript e la DLL compilata vengono uniti in un unico pacchetto, di cui è possibile eseguire il debug in locale nel simulatore o in remoto in un dispositivo con tethering. È anche possibile distribuire solo il progetto componente come SDK di estensione. Per altre informazioni, vedere Creating a Software Development Kit (Creazione di un Software Development Kit).

In generale, quando si codifica il componente C++/CX, usare la normale libreria C++ e i tipi predefiniti, ad eccezione del limite dell'interfaccia binaria astratta (ABI) in cui si passano dati da e verso il codice in un altro pacchetto .winmd. In questo caso, usare i tipi Windows Runtime e la sintassi speciale supportata da C++/CX per la creazione e la manipolazione di tali tipi. Inoltre, nel codice C++/CX, usare tipi come delegato ed evento per implementare eventi che possono essere generati dal componente e gestiti in JavaScript, Visual Basic, C++o C#. Per altre informazioni sulla sintassi C++/CX, vedere Visual C++ Language Reference (C++/CX).

Regole di denominazione e di maiuscole e minuscole

JavaScript

JavaScript fa distinzione tra maiuscole e minuscole. Pertanto, è necessario seguire queste convenzioni di maiuscole e minuscole:

  • Quando si fa riferimento a spazi dei nomi e classi C++, usare la stessa combinazione di maiuscole e minuscole usata sul lato C++.
  • Quando si chiamano i metodi, usare maiuscole e minuscole camel anche se il nome del metodo è in maiuscolo sul lato C++. Ad esempio, un metodo C++ GetDate() deve essere chiamato da JavaScript come getDate().
  • Un nome di classe attivabile e un nome dello spazio dei nomi non possono contenere caratteri UNICODE.

.NET

I linguaggi .NET seguono le normali regole di maiuscole e minuscole.

Creazione di un'istanza dell'oggetto

Solo i tipi Windows Runtime possono essere passati attraverso il limite ABI. Il compilatore genererà un errore se il componente ha un tipo come std::wstring come tipo restituito o parametro in un metodo pubblico. I tipi predefiniti C++ component extensions (C++/CX) includono i normali scalari, ad esempio int e double, e anche i relativi equivalenti typedef int32, float64 e così via. Per altre informazioni, vedere 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();

Tipi predefiniti C++/CX, tipi di libreria e tipi di Windows Runtime

Una classe attivabile (nota anche come classe di riferimento) è una classe di cui è possibile creare un'istanza da un altro linguaggio, ad esempio JavaScript, C# o Visual Basic. Per essere utilizzabile da un altro linguaggio, un componente deve contenere almeno una classe attivabile.

Un componente Windows Runtime può contenere più classi attivabili pubbliche, nonché classi aggiuntive note solo internamente al componente. Applicare l'attributo WebHostHidden ai tipi C++/CX che non devono essere visibili a JavaScript.

Tutte le classi pubbliche devono trovarsi nello stesso spazio dei nomi radice con lo stesso nome del file di metadati del componente. Ad esempio, è possibile creare un'istanza di una classe denominata A.B.C.MyClass solo se è definita in un file di metadati denominato A.winmd o A.B.winmd o A.B.C.winmd. Il nome della DLL non deve necessariamente corrispondere al nome del file con estensione winmd.

Il codice client crea un'istanza del componente usando la parola chiave new (New in Visual Basic) esattamente come per qualsiasi classe.

Una classe attivabile deve essere dichiarata come classe di riferimento pubblica sealed. La parola chiave ref class indica al compilatore di creare la classe come tipo compatibile con Windows Runtime e la parola chiave sealed specifica che la classe non può essere ereditata. Windows Runtime attualmente non supporta un modello di ereditarietà generalizzato; un modello di ereditarietà limitato supporta la creazione di controlli XAML personalizzati. Per altre informazioni, vedere Classi e struct di riferimento (C++/CX).

Per C++/CX, tutte le primitive numeriche sono definite nello spazio dei nomi predefinito. Lo spazio dei nomi Platform contiene classi C++/CX specifiche del sistema di tipi Windows Runtime. Sono incluse la classe Platform::String e la classe Platform::Object. I tipi di raccolta concreta, ad esempio Platform::Collections::Map e Platform::Collections::Vector, sono definiti nello spazio dei nomi Platform::Collections . Le interfacce pubbliche implementate da questi tipi sono definite in Windows::Foundation::Collections Namespace (C++/CX). Si tratta di questi tipi di interfaccia utilizzati da JavaScript, C# e Visual Basic. Per altre informazioni, vedere Type System (C++/CX).

Metodo che restituisce un valore di tipo predefinito

    // #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;

Metodo che restituisce uno struct di valore personalizzato

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

Per passare gli struct valore definiti dall'utente nell'ABI, definire un oggetto JavaScript con gli stessi membri dello struct di valore definito in C++/CX. È quindi possibile passare tale oggetto come argomento a un metodo C++/CX in modo che l'oggetto venga convertito in modo implicito nel tipo 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);
}

Un altro approccio consiste nel definire una classe che implementa IPropertySet (non visualizzata).

Nei linguaggi .NET è sufficiente creare una variabile del tipo definito nel componente 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();
}

Metodi di overload

Una classe di riferimento pubblica C++/CX può contenere metodi di overload, ma JavaScript ha una capacità limitata di distinguere i metodi di overload. Ad esempio, può indicare la differenza tra queste firme:

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

Ma non è in grado di indicare la differenza tra questi:

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

In casi ambigui, è possibile assicurarsi che JavaScript chiami sempre un overload specifico applicando l'attributo Windows::Foundation::Metadata::D efaultOverload alla firma del metodo nel file di intestazione.

Questo Codice JavaScript chiama sempre l'overload attribuito:

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

.NET

I linguaggi .NET riconoscono gli overload in una classe di riferimento C++/CX esattamente come in qualsiasi classe .NET.

Data/Ora

In Windows Runtime, un oggetto Windows::Foundation::DateTime è solo un intero con segno a 64 bit che rappresenta il numero di intervalli di 100 nanosecondi prima o dopo il 1 gennaio 1601. Non esistono metodi in un oggetto Windows:Foundation::DateTime. Ogni linguaggio proietta invece DateTime nel modo nativo di tale linguaggio: l'oggetto Date in JavaScript e i tipi System.DateTime e System.DateTimeOffset in .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;
    }
};

Quando si passa un valore DateTime da C++/CX a JavaScript, JavaScript lo accetta come oggetto Date e lo visualizza per impostazione predefinita come stringa di data long-form.

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;

}

Quando un linguaggio .NET passa System.DateTime a un componente C++/CX, il metodo lo accetta come Windows::Foundation::DateTime. Quando il componente passa un oggetto Windows::Foundation::DateTime a un metodo .NET, il metodo Framework lo accetta come 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();
}

Raccolte e matrici

Le raccolte vengono sempre passate attraverso il limite ABI come handle per i tipi Windows Runtime, ad esempio Windows::Foundation::Collections::IVector^ e Windows::Foundation::Collections::IMap^. Ad esempio, se si restituisce un handle a un oggetto Platform::Collections::Map, viene convertito in modo implicito in windows::Foundation::Collections::IMap^. Le interfacce di raccolta sono definite in uno spazio dei nomi separato dalle classi C++/CX che forniscono le implementazioni concrete. I linguaggi JavaScript e .NET usano le interfacce. Per altre informazioni, vedere Raccolte (C++/CX) e Array e WriteOnlyArray (C++/CX).

Passaggio di 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;

I linguaggi .NET vedono IVector<T> come 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();
    }
}

Passaggio di 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;

I linguaggi .NET vedono IMap e IDictionary<K, V>.

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

Proprietà

Una classe di riferimento pubblica nelle estensioni del componente C++/CX espone i membri dati pubblici come proprietà, usando la parola chiave property. Il concetto è identico alle proprietà .NET. Una proprietà semplice è simile a un membro dati perché la relativa funzionalità è implicita. Una proprietà non semplice ha funzioni di accesso get e set esplicite e una variabile privata denominata che rappresenta l'archivio di backup per il valore. In questo esempio, la variabile membro privato _propertyAValue è l'archivio di backup per PropertyA. Una proprietà può generare un evento quando il valore cambia e un'app client può registrarsi per ricevere tale evento.

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

I linguaggi .NET accedono alle proprietà in un oggetto C++/CX nativo esattamente come in un oggetto .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;

}

Delegati ed eventi

Un delegato è un tipo di Windows Runtime che rappresenta un oggetto funzione. È possibile usare delegati in connessione con eventi, callback e chiamate asincrone di metodi per specificare un'azione da eseguire in un secondo momento. Analogamente a un oggetto funzione, il delegato fornisce la sicurezza dei tipi consentendo al compilatore di verificare il tipo restituito e i tipi di parametro della funzione. La dichiarazione di un delegato è simile a una firma di funzione, l'implementazione è simile a una definizione di classe e la chiamata è simile a una chiamata di funzione.

Aggiunta di un listener di eventi

È possibile usare la parola chiave event per dichiarare un membro pubblico di un tipo delegato specificato. Il codice client sottoscrive l'evento usando i meccanismi standard forniti nella lingua specifica.

public:
    event SomeHandler^ someEvent;

In questo esempio viene usato lo stesso codice C++ usato per la sezione precedente delle proprietà.

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;

}

Nei linguaggi .NET la sottoscrizione di un evento in un componente C++ equivale alla sottoscrizione di un evento in una 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();
}

Aggiunta di più listener di eventi per un evento

JavaScript include un metodo addEventListener che consente a più gestori di sottoscrivere un singolo evento.

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 ");

In C# qualsiasi numero di gestori eventi può sottoscrivere l'evento usando l'operatore += come illustrato nell'esempio precedente.

Enumerazioni

Un'enumerazione di Windows Runtime in C++/CX viene dichiarata usando l'enumerazione della classe pubblica; è simile a un'enumerazione con ambito in 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;
};

I valori di enumerazione vengono passati tra C++/CX e JavaScript come numeri interi. Facoltativamente, è possibile dichiarare un oggetto JavaScript contenente gli stessi valori denominati dell'enumerazione C++/CX e usarlo come indicato di seguito.

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

Sia C# che Visual Basic supportano il linguaggio per le enumerazioni. Questi linguaggi visualizzano una classe di enumerazione pubblica C++ proprio come se visualizzassero un'enumerazione .NET.

Metodi asincroni

Per utilizzare metodi asincroni esposti da altri oggetti Windows Runtime, usare la classe dell'attività (runtime di concorrenza). Per ulteriori informazioni, vedere e Parallelismo delle attività (runtime di concorrenza).

Per implementare metodi asincroni in C++/CX, usare la funzione create_async definita in ppltasks.h. Per altre informazioni, vedere Creazione di operazioni asincrone in C++/CX per le app UWP. Per un esempio, vedere la procedura dettagliata per la creazione di un componente Windows Runtime C++/CX e chiamata del componente da JavaScript o C#. I linguaggi .NET usano metodi asincroni C++/CX esattamente come qualsiasi metodo asincrono definito in .NET.

Eccezioni

È possibile generare qualsiasi tipo di eccezione definito da Windows Runtime. Non è possibile derivare tipi personalizzati da qualsiasi tipo di eccezione di Windows Runtime. Tuttavia, è possibile generare COMException e fornire un HRESULT personalizzato accessibile dal codice che intercetta l'eccezione. Non è possibile specificare un messaggio personalizzato in un'eccezione COMException.

Suggerimenti per il debug

Quando si esegue il debug di una soluzione JavaScript con una DLL del componente, è possibile impostare il debugger per abilitare l'esecuzione dello script o l'esecuzione di codice nativo nel componente, ma non entrambi contemporaneamente. Per modificare l'impostazione, selezionare il nodo del progetto JavaScript in Esplora soluzioni e quindi selezionare Proprietà, Debug, Tipo di debugger.

Assicurarsi di selezionare le funzionalità appropriate nella finestra di progettazione pacchetti. Ad esempio, se si sta tentando di aprire un file di immagine nella raccolta immagini dell'utente usando le API di Windows Runtime, assicurarsi di selezionare la casella di controllo Raccolta immagini nel riquadro Funzionalità della finestra di progettazione del manifesto.

Se il codice JavaScript non sembra riconoscere le proprietà o i metodi pubblici nel componente, assicurarsi che in JavaScript si usi la combinazione di maiuscole e minuscole camel. Ad esempio, è necessario fare riferimento al metodo LogCalc C++/CX come logCalc in JavaScript.

Se si rimuove un progetto di componente Windows Runtime C++/CX da una soluzione, si deve anche rimuovere manualmente il riferimento al progetto dal progetto JavaScript. In caso contrario, impedisce le operazioni di debug o compilazioni successive. Se necessario, è quindi possibile aggiungere un riferimento di gruppo alla DLL.