Creazione di componenti Windows Runtime in C++
In questo articolo viene illustrato come utilizzare C++ per creare un componente Windows Runtime, ovvero una DLL che è possibile chiamare da un'app di Windows Store compilata mediante JavaScript (o C#, Visual Basic, C++). Ecco i diversi motivi per cui compilare un componente di questo tipo:
Sfruttare il vantaggio in termini di prestazioni offerto da C++ nelle operazioni difficili o complesse dal punto di vista dell'elaborazione.
Riutilizzare codice già scritto e testato.
Quando compili una soluzione contenente un progetto JavaScript o .NET e un progetto componente Windows Runtime, i file di progetto JavaScript e il file DLL compilato vengono uniti in un pacchetto, di cui puoi eseguire il debug in locale nel simulatore o in remoto in un dispositivo con tethering. Puoi anche distribuire solo il progetto di componente come estensione SDK. Per ulteriori informazioni, vedi Procedura: creare una kit di sviluppo software.
In generale, quando codifichi il componente C++, utilizza i tipi predefiniti e la libreria C++ normale, tranne in corrispondenza del limite dell'ABI (Abstract Binary Interface) dove passi dati a e dal codice in un altro pacchetto .winmd. Qui utilizza tipi di Windows Runtime e la sintassi speciale supportata da Visual C++ per creare e modificare questi tipi. Inoltre, nel codice di Visual C++ utilizza tipi come delegate ed event per implementare eventi che possono essere generati dal componente e gestiti in JavaScript, Visual Basic o C#. Per ulteriori informazioni sulla nuova sintassi di Visual C++, vedi Riferimenti al linguaggio Visual C++ (C++/CX).
Regole di denominazione e combinazione di maiuscole e minuscole
JavaScript
JavaScript rileva la distinzione tra maiuscole e minuscole. Devi pertanto attenerti a queste convenzioni sulla combinazione di maiuscole e minuscole:
Quando fai riferimento agli spazi dei nomi e alle classi C++, utilizza la stessa distinzione tra maiuscole e minuscole utilizzata per C++.
Per chiamare i metodi, utilizza la convenzione Camel anche se il nome del metodo ha l'iniziale maiuscola in C++. Ad esempio, un metodo GetDate() C++ deve essere chiamato come getDate() da JavaScript.
Un nome di classe e un nome di spazio dei nomi attivabili non possono contenere caratteri UNICODE.
.NET
I linguaggi .NET seguono le regole standard di utilizzo di maiuscole e minuscole.
Creazione di un'istanza dell'oggetto
Solo i tipi di Windows Runtime possono essere passati attraverso il limite dell'ABI. Il compilatore genererà un errore se il componente dispone di un tipo quale std::wstring come tipo restituito o parametro in un metodo pubblico. I tipi incorporati Estensioni componenti Visual C++ (C++/CX) includono gli scalari normali come int e double, nonché i relativi equivalenti typedeg int32, float64 e così via. Per ulteriori informazioni, vedi Sistema di tipi (C++/CX).
C++
// 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);
}
};
JavaScript
//Instantiation in JavaScript (requires "Add reference > Project reference")
var nativeObject = new CppComponent.SampleRefClass();
.NET
//Call a method and display result in a XAML TextBlock
var num = nativeObject.LogCalc(21.5);
ResultText.Text = num.ToString();
Tipi predefiniti di C++, tipi di librerie e tipi di Windows Runtime
Una classe attivabile, anche nota 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 pubbliche attivabili, nonché classi aggiuntive note solo internamente al componente. Tutte le classi pubbliche devono essere incluse nello stesso spazio dei nomi radice con lo stesso nome del file di metadati del componente. Applica l'attributo [WebHostHidden] ai tipi C++ che non intendi rendere visibili a JavaScript.
Il codice client crea un'istanza del componente utilizzando la parola chiave new (New in Visual Basic) come per qualsiasi classe.
Una classe attivabile deve essere dichiarata come public ref class sealed. La parola chiave della classe ref indica al compilatore di creare la classe come tipo compatibile di Windows Runtime e la parola chiave sealed specifica che la classe non può essere ereditata. In Windows Runtime non è al momento supportato un modello di ereditarietà generalizzato; un modello di ereditarietà limitato supporta la creazione di controlli XAML personalizzati. Per ulteriori informazioni, vedi Classi e struct di riferimento (C++/CX).
Per C++ tutte le primitive numeriche sono definite nello spazio dei nomi predefinito. Platform Namespace contiene le classi C++ specifiche del sistema dei tipi di Windows Runtime. Sono incluse Classe Platform::String e Classe Platform::Object. I tipi di raccolta concreti come Classe Platform::Collections::Map e Classe Platform::Collections::Vector sono definiti nello Spazio dei nomi Platform::Collections. Le interfacce pubbliche implementate da questi tipi sono definite in Spazio dei nomi Windows::Foundation::Collections (C++/CX). Questi tipi di interfaccia vengono utilizzati da JavaScript, C# e Visual Basic. Per ulteriori informazioni, vedi Sistema di tipi (C++/CX).
Metodo che restituisce un valore di tipo predefinito
C++
// #include <valarray>
public:
double LogCalc(double input)
{
// Use C++ standard library as usual.
return std::log(input);
}
JavaScript
//Call a method
var nativeObject = new CppComponent.SampleRefClass;
var num = nativeObject.logCalc(21.5);
document.getElementById('P2').innerHTML = num;
.NET
Metodo che restituisce uno struct di valore personalizzato
value struct è un oggetto POD (Plain old data, dati non aggiornati) che può contenere i campi pubblici per impostazione predefinita. value struct viene passato per valore.
C++
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;}
}
};
}
JavaScript
Per passare struct di valore definito dall'utente attraverso l'ABI, definisci un oggetto JavaScript con gli stessi membri dello struct di valore definito in C++. Puoi quindi passare tale oggetto come argomento a un metodo C++ in modo da convertire l'oggetto in modo implicito nel tipo C++.
// 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 illustrato).
C#
Nei linguaggi .NET viene semplicemente creata una variabile del tipo definito nel componente C++.
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++ può contenere metodi di overload, ma JavaScript limita la possibilità di differenziare 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);
};
Non può invece indicare la differenza tra queste:
int GetNumber(int i);
double GetNumber(double d);
o queste:
In casi ambigui puoi assicurarti che JavaScript chiami sempre un overload specifico applicando l'attributo Windows::Foundation::Metadata::DefaultOverload alla firma del metodo nel file di intestazione.
Questo codice JavaScript chiama sempre l'overload con attributi:
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++ come in qualsiasi classe .NET Framework.
DateTime
In Windows Runtime un oggetto Windows::Foundation::DateTime è semplicemente un intero con segno a 64 bit che rappresenta il numero di intervalli di 100 nanosecondi precedenti o successivi al 1° gennaio 1601. Non sono presenti metodi in un oggetto Windows:Foundation::DateTime. Al contrario, ogni linguaggio progetta DateTime in modalità nativa per il linguaggio stesso: l'oggetto Date in JavaScript e i tipi System.DateTime e System.DateTimeOffset in .NET Framework.
C++
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;
}
};
JavaScript
Quando passi un valore DateTime da C++ a JavaScript, JavaScript lo accetta come oggetto Date e lo visualizza per impostazione predefinita come una stringa di data in formato esteso.
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;
}
.NET
Quando un linguaggio .NET passa System.DateTime al componente C++, il metodo la accetta come Windows::Foundation::DateTime. Quando il componente passa Windows::Foundation::DateTime a un metodo .NET Framework, 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 dell'ABI come handle di tipi Windows Runtime, ad esempio Windows::Foundation::Collections::IVector^ e Windows::Foundation::Collections::IMap^. Se a esempio restituisci un handle a Platform::Collections::Map, verrà convertito in modo implicito in Windows::Foundation::Collections::IMap^. Le interfacce di raccolta sono definite in uno spazio dei nomi separato dalle classi C++ che forniscono le implementazioni concrete. I linguaggi .NET e JavaScript utilizzano le interfacce. Per ulteriori informazioni, vedi Raccolte (C++/CX) e Array e WriteOnlyArray (C++/CX).
Passaggio di IVector
C++
// 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;
}
JavaScript
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;
.NET
I linguaggi .NET considerano 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
C++
// #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;
}
JavaScript
// 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;
.NET
I linguaggi .NET considerano IMap come 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 in Estensioni componenti Visual C++ espone membri dati pubblici come proprietà, utilizzando la parola chiave property. Il concetto è identico alle proprietà .NET Framework. Una proprietà semplice è simile a un membro dati, poiché le relative funzionalità sono implicite. Una proprietà non semplice ha funzioni di accesso get e set esplicite e una variabile privata denominata che è l'archivio di backup per il valore. In questo esempio, il membro privato variable _propertyAValue è l'archivio di backup per PropertyA. Una proprietà può generare un evento quando ne viene modificato il valore e un'app client può registrarsi per ricevere l'evento.
C++
//Properties
public delegate void PropertyChangedHandler(Platform::Object^ sender, int arg);
public ref class PropertyExample sealed
{
public:
PropertyExample(){}
// Event that is fired when PropetyA 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;
};
JavaScript
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;
.NET
I linguaggi .NET accedono alle proprietà in un oggetto C++ nativo in modo analogo a quanto si verifica con un oggetto .NET Framework.
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 delegate è un tipo di Windows Runtime che rappresenta un oggetto funzione. Puoi utilizzare i delegati insieme a eventi, callback e chiamate asincrone a metodi per specificare un'azione da eseguire in un secondo momento. Come un oggetto funzione, il delegato fornisce l'indipendenza dai tipi permettendo al compilatore di verificare il tipo restituito e i tipi di parametro della funzione. La dichiarazione di un delegato è simile a una firma della funzione, l'implementazione alla definizione di una classe e la chiamata a una chiamata di funzione.
Aggiunta di un listener di eventi
Puoi utilizzare la parola chiave event per dichiarare un membro pubblico di un tipo delegato specificato. Il codice client sottoscrive l'evento tramite i meccanismi standard forniti nel linguaggio in uso.
C++
public:
event SomeHandler^ someEvent;
In questo esempio viene utilizzato lo stesso codice C++ della sezione di proprietà precedente.
JavaScript
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;
}
.NET
Nei linguaggi .NET la sottoscrizione a un evento in un componente C++ è identica alla sottoscrizione a un evento in una classe .NET Framework:
//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 prevede un metodo addEventListener che consente a più handler di sottoscrivere un singolo evento.
C++
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()));
}
//...
};
JavaScript
// 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 ");
.NET
In C# un numero qualsiasi di gestori eventi possono eseguire la sottoscrizione all'evento mediante l'operatore +=, come illustrato nell'esempio precedente.
Enumerazioni
Un'enumerazione Windows Runtime in C++ viene dichiarata utilizzando public class enum; è simile a un'enumerazione con ambito in C++ standard.
C++
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;
};
JavaScript
I valori enum sono passati tra C++ e JavaScript come Integer. Puoi anche dichiarare un oggetto JavaScript che contiene gli stessi valori denominati dell'enumerazione C++ e utilizzarlo 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];
.NET
Sia in C# che in Visual Basic è previsto il supporto di linguaggi per le enumerazioni. Questi linguaggi considerano una classe di enumerazione pubblica C++ come un'enumerazione .NET Framework.
Metodi asincroni
Per utilizzare metodi asincroni esposti da altri oggetti Windows Runtime, utilizza Classe task (runtime di concorrenza). Per ulteriori informazioni, vedi Asychronous Programming in C++ e Parallelismo delle attività (runtime di concorrenza).
Per implementare metodi asincroni in C++, utilizza la funzione create_async definita in ppltasks.h. Per ulteriori informazioni, vedi Creazione di operazioni asincrone in C++ per le applicazioni Windows Store. Per un esempio, vedi Procedura dettagliata: creazione di un componente Windows Runtime di base in C++ e chiamata da JavaScript. I linguaggi .NET utilizzano metodi asincroni C++ in modo analogo a quanto si verifica con qualsiasi metodo asincrono definito in .NET Framework.
Eccezioni
Puoi generare qualsiasi tipo di eccezione definito da Windows Runtime. Non puoi derivare tipi personalizzati da alcun tipo di eccezione di Windows Runtime. Puoi tuttavia generare COMException e fornire HRESULT personalizzato, accessibile dal codice che intercetta l'eccezione. Non puoi specificare un messaggio personalizzato in COMException.
Suggerimenti relativi al debug
Quando esegui il debug di una soluzione JavaScript che dispone di una DLL componente, puoi impostare il debugger per abilitare l'esecuzione passo passo tramite script o l'esecuzione passo passo nel codice nativo nel componente, ma non entrambe contemporaneamente. Per modificare l'impostazione, seleziona il nodo del progetto JavaScript in Esplora soluzioni, quindi scegli Proprietà, Debug, Tipo di debugger.
Assicurati di selezionare le funzionalità appropriate nella finestra di progettazione del pacchetto. Se ad esempio tenti di aprire un file utilizzando le API Windows Runtime, accertati di selezionare la casella di controllo Accesso raccolta documenti nel riquadro Funzionalità della finestra di progettazione del pacchetto.
Se il codice JavaScript non sembra riconoscere le proprietà o i metodi pubblici nel componente, verifica di utilizzare la convenzione Camel in JavaScript. Ad esempio, è necessario fare riferimento al metodo LogCalc C++ come logCalc in JavaScript.
Se rimuovi il progetto componente Windows Runtime di C++ da una soluzione, devi anche rimuovere manualmente il riferimento al progetto dal progetto JavaScript. In caso contrario non potrai eseguire le operazioni di debug o compilazione successive. Se necessario, puoi aggiungere un riferimento all'assembly alla DLL.