Componentes de Windows Runtime con C++/CX
Nota:
Este tema existe para ayudarte a mantener la aplicación C++/CX. Pero te recomendamos que uses C++/WinRT para las nuevas aplicaciones. C++/WinRT es una moderna proyección de lenguaje C++17 totalmente estándar para las API de Windows Runtime (WinRT), implementada como una biblioteca basada en archivos de encabezado y diseñada para darte acceso de primera clase a la API moderna de Windows. Para obtener información sobre cómo crear un componente de Windows Runtime con C++/WinRT, consulte Componentes de Windows Runtime con C++/WinRT.
En este tema se muestra cómo usar C++/CX para crear un componente de Windows Runtime, un componente al que se puede llamar desde una aplicación universal de Windows compilada con cualquier lenguaje de Windows Runtime (C#, Visual Basic, C++o JavaScript).
Hay varias razones para crear un componente de Windows Runtime en C++.
- Para obtener la ventaja de rendimiento de C++ en operaciones complejas o de uso intensivo computacional.
- Para reutilizar el código que ya está escrito y probado.
Al compilar una solución que contiene un proyecto de JavaScript o .NET, y un proyecto de componente de Windows Runtime, los archivos de proyecto de JavaScript y el archivo DLL compilado se combinan en un paquete, que puede depurar localmente en el simulador o de forma remota en un dispositivo anclado. También puede distribuir solo el proyecto de componente como un SDK de extensión. Para más información, vea Crear un Kit de desarrollo de Software.
En general, al codificar el componente de C++/CX, use la biblioteca normal de C++ y los tipos integrados, excepto en el límite de la interfaz binaria abstracta (ABI) donde se pasan datos al código y desde el código en otro paquete .winmd. Allí, usa tipos de Windows Runtime y la sintaxis especial que C++/CX admite para crear y manipular esos tipos. Además, en el código de C++/CX, use tipos como delegado y evento para implementar eventos que se pueden generar desde el componente y controlarse en JavaScript, Visual Basic, C++o C#. Para obtener más información sobre la sintaxis de C++/CX, vea Referencia del lenguaje Visual C++ (C++/CX).
Reglas de nomenclatura y mayúsculas y minúsculas
JavaScript
JavaScript distingue mayúsculas de minúsculas. Por lo tanto, debe seguir estas convenciones de mayúsculas y minúsculas:
- Al hacer referencia a los espacios de nombres y clases de C++, use el mismo uso de mayúsculas y minúsculas en el lado de C++.
- Al llamar a métodos, use mayúsculas y minúsculas incluso si el nombre del método se escribe en mayúsculas en el lado de C++. Por ejemplo, se debe llamar a un método GetDate() de C++ desde JavaScript como getDate().
- Un nombre de clase activable y un nombre de espacio de nombres no pueden contener caracteres UNICODE.
.NET
Los lenguajes .NET siguen sus reglas de mayúsculas y minúsculas normales.
Creación de instancias del objeto
Solo los tipos de Windows Runtime se pueden pasar a través del límite de ABI. El compilador generará un error si el componente tiene un tipo como std::wstring como un tipo de valor devuelto o un parámetro en un método público. Los tipos integrados de extensiones de componentes de Visual C++ (C++/CX) incluyen los escalares habituales, como int y double, y también sus equivalentes typedef int32, float64, etc. Para obtener más información, vea 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();
Tipos integrados de C++/CX, tipos de biblioteca y tipos de Windows Runtime
Una clase activable (también conocida como clase ref) es una que se puede crear una instancia desde otro lenguaje, como JavaScript, C# o Visual Basic. Para ser consumible desde otro lenguaje, un componente debe contener al menos una clase activable.
Un componente de Windows Runtime puede contener varias clases activables públicas, así como clases adicionales que solo se conocen internamente al componente. Aplique el atributo WebHostHidden a los tipos de C++/CX que no están diseñados para ser visibles para JavaScript.
Todas las clases públicas deben residir en el mismo espacio de nombres raíz que tiene el mismo nombre que el archivo de metadatos del componente. Por ejemplo, se pueden crear instancias de una clase denominada A.B.C.MyClass solo si está definida en un archivo de metadatos denominado A.winmd, A.B.winmd o A.B.C.winmd. El nombre de la DLL no tiene que coincidir con el nombre del archivo .winmd.
El código de cliente crea una instancia del componente mediante la palabra clave new (New in Visual Basic) igual que para cualquier clase.
Una clase activable debe declararse como clase ref pública sellada. La palabra clave ref class indica al compilador que cree la clase como un tipo compatible con Windows Runtime y la palabra clave sealed especifica que la clase no se puede heredar. Windows Runtime no admite actualmente un modelo de herencia generalizado; Un modelo de herencia limitado admite la creación de controles XAML personalizados. Para obtener más información, vea Clases y estructuras ref (C++/CX).
Para C++/CX, todos los primitivos numéricos se definen en el espacio de nombres predeterminado. El espacio de nombres Platform contiene clases de C++/CX específicas del sistema de tipos de Windows Runtime. Estas incluyen la clase Platform::String y la clase Platform::Object . Los tipos de colección concretos, como la clase Platform::Collections::Map y la clase Platform::Collections::Vector , se definen en el espacio de nombres Platform::Collections . Las interfaces públicas que implementan estos tipos se definen en Windows::Foundation::Collections Namespace (C++/CX). Se trata de estos tipos de interfaz consumidos por JavaScript, C# y Visual Basic. Para obtener más información, vea Type System (C++/CX).
Método que devuelve un valor de tipo integrado
// #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étodo que devuelve una estructura de valor personalizado
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;}
}
};
}
Para pasar estructuras de valor definidas por el usuario a través de la ABI, defina un objeto JavaScript que tenga los mismos miembros que la estructura de valor definida en C++/CX. A continuación, puede pasar ese objeto como argumento a un método de C++/CX para que el objeto se convierta implícitamente en el 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);
}
Otro enfoque consiste en definir una clase que implemente IPropertySet (no se muestra).
En los lenguajes .NET, solo tiene que crear una variable del tipo definido en el componente de 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étodos sobrecargados
Una clase ref pública de C++/CX puede contener métodos sobrecargados, pero JavaScript tiene una capacidad limitada para diferenciar los métodos sobrecargados. Por ejemplo, puede indicar la diferencia entre estas firmas:
public ref class NumberClass sealed
{
public:
int GetNumber(int i);
int GetNumber(int i, Platform::String^ str);
double GetNumber(int i, MyData^ d);
};
Pero no puede decir la diferencia entre estas:
int GetNumber(int i);
double GetNumber(double d);
En casos ambiguos, puede asegurarse de que JavaScript siempre llama a una sobrecarga específica aplicando el atributo Windows::Foundation::Metadata::D efaultOverload a la firma del método en el archivo de encabezado.
Este JavaScript siempre llama a la sobrecarga con atributos:
var nativeObject = new CppComponent.NumberClass();
var num = nativeObject.getNumber(9);
document.getElementById('P4').innerHTML = num;
.NET
Los lenguajes .NET reconocen sobrecargas en una clase ref de C++/CX igual que en cualquier clase de .NET.
DateTime
En Windows Runtime, un objeto Windows::Foundation::D ateTime es solo un entero de 64 bits con signo que representa el número de intervalos de 100 nanosegundos antes o después del 1 de enero de 1601. No hay métodos en un objeto Windows:Foundation::D ateTime. En su lugar, cada lenguaje proyecta dateTime de la forma nativa de ese lenguaje: el objeto Date en JavaScript y los tipos System.DateTime y System.DateTimeOffset en .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;
}
};
Cuando se pasa un valor DateTime de C++/CX a JavaScript, JavaScript lo acepta como un objeto Date y lo muestra de forma predeterminada como una cadena de fecha de formato largo.
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;
}
Cuando un lenguaje .NET pasa system.DateTime a un componente de C++/CX, el método lo acepta como Windows::Foundation::D ateTime. Cuando el componente pasa windows::Foundation::D ateTime a un método .NET, el método Framework lo acepta como 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();
}
Colecciones y matrices
Las colecciones siempre se pasan a través del límite de ABI como identificadores a tipos de Windows Runtime, como Windows::Foundation::Collections::IVector^ y Windows::Foundation::Collections::IMap^. Por ejemplo, si devuelve un identificador a Platform::Collections::Map, se convierte implícitamente en windows::Foundation::Collections::IMap^. Las interfaces de colección se definen en un espacio de nombres independiente de las clases de C++/CX que proporcionan las implementaciones concretas. Los lenguajes JavaScript y .NET consumen las interfaces. Para obtener más información, vea Colecciones (C++/CX) y Matriz y WriteOnlyArray (C++/CX).
Pasar 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;
Los lenguajes .NET ven IVector<T> como 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();
}
}
Pasar 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;
Los lenguajes .NET ven IMap e IDictionary<K, V>.
private void GetDictionary()
{
var nativeObject = new CppComponent.CollectionExample();
IDictionary<int, string> d = nativeObject.GetMap();
ResultText.Text += d[2].ToString();
}
Propiedades
Una clase ref pública en las extensiones de componentes de C++/CX expone miembros de datos públicos como propiedades mediante la palabra clave property. El concepto es idéntico a las propiedades de .NET. Una propiedad trivial es similar a un miembro de datos porque su funcionalidad es implícita. Una propiedad no trivial tiene descriptores de acceso get y set explícitos y una variable privada con nombre que es el "almacén de respaldo" para el valor. En este ejemplo, la variable de miembro privado _propertyAValue es el almacén de respaldo de PropertyA. Una propiedad puede desencadenar un evento cuando cambia su valor y una aplicación cliente puede registrarse para recibir ese 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;
Los lenguajes .NET acceden a las propiedades de un objeto nativo de C++/CX como lo harían en un objeto .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;
}
Delegados y eventos
Un delegado es un tipo de Windows Runtime que representa un objeto de función. Puede usar delegados en conexión con eventos, devoluciones de llamada y llamadas de método asincrónico para especificar una acción que se va a realizar más adelante. Al igual que un objeto de función, el delegado proporciona seguridad de tipos al permitir que el compilador compruebe el tipo de valor devuelto y los tipos de parámetro de la función. La declaración de un delegado es similar a una firma de función, la implementación se parece a una definición de clase y la invocación es similar a una invocación de función.
Adición de un agente de escucha de eventos
Puede usar la palabra clave event para declarar un miembro público de un tipo de delegado especificado. El código de cliente se suscribe al evento mediante los mecanismos estándar que se proporcionan en el idioma concreto.
public:
event SomeHandler^ someEvent;
En este ejemplo se usa el mismo código de C++ que para la sección de propiedades anteriores.
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;
}
En los lenguajes .NET, la suscripción a un evento en un componente de C++ es la misma que la suscripción a un evento en una clase .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();
}
Adición de varios agentes de escucha de eventos para un evento
JavaScript tiene un método addEventListener que permite a varios controladores suscribirse a un único 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 ");
En C#, cualquier número de controladores de eventos puede suscribirse al evento mediante el operador += como se muestra en el ejemplo anterior.
Enumeraciones
Una enumeración de Windows Runtime en C++/CX se declara mediante la enumeración de clase pública; se parece a una enumeración con ámbito en C++estándar.
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;
};
Los valores de enumeración se pasan entre C++/CX y JavaScript como enteros. Opcionalmente, puede declarar un objeto JavaScript que contenga los mismos valores con nombre que la enumeración de C++/CX y usarlo como se indica a continuación.
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];
Tanto C# como Visual Basic tienen compatibilidad con lenguajes para enumeraciones. Estos lenguajes ven una clase de enumeración pública de C++ igual que verían una enumeración de .NET.
Métodos asincrónicos
Para consumir métodos asincrónicos expuestos por otros objetos de Windows Runtime, use la clase de tarea (runtime de simultaneidad) . Para obtener más información, consulte y Task Parallelism (Runtime de simultaneidad) .
Para implementar métodos asincrónicos en C++/CX, use la función create_async definida en ppltasks.h. Para obtener más información, consulta Crear operaciones asincrónicas en C++/CX para aplicaciones para UWP. Para obtener un ejemplo, vea Tutorial sobre cómo crear un componente de Windows Runtime de C++/CX y llamarlo desde JavaScript o C#. Los lenguajes .NET consumen métodos asincrónicos de C++/CX igual que los métodos asincrónicos definidos en .NET.
Excepciones
Puedes iniciar cualquier tipo de excepción definido por Windows Runtime. No se pueden derivar tipos personalizados de ningún tipo de excepción de Windows Runtime. Sin embargo, puede iniciar COMException y proporcionar un HRESULT personalizado al que puede acceder el código que captura la excepción. No hay ninguna manera de especificar un mensaje personalizado en una excepción COMException.
Sugerencias de depuración
Al depurar una solución de JavaScript que tenga un archivo DLL de componente, puede establecer el depurador para habilitar el script paso a paso o recorrer el código nativo en el componente, pero no ambos al mismo tiempo. Para cambiar la configuración, seleccione el nodo del proyecto de JavaScript en Explorador de soluciones y, a continuación, elija Propiedades, Depuración, Tipo de depurador.
Asegúrese de seleccionar las funcionalidades adecuadas en el diseñador de paquetes. Por ejemplo, si intenta abrir un archivo de imagen en la biblioteca imágenes del usuario mediante las API de Windows Runtime, asegúrese de activar la casilla Biblioteca de imágenes en el panel Funcionalidades del diseñador de manifiestos.
Si el código javaScript no parece reconocer las propiedades o métodos públicos del componente, asegúrese de que en JavaScript usa mayúsculas y minúsculas. Por ejemplo, se debe hacer referencia al método LogCalc C++/CX como logCalc en JavaScript.
Si quitas un proyecto de componente de Windows Runtime de C++/CX de una solución, también debes quitar manualmente la referencia del proyecto de JavaScript. Si no lo hace, se evitan las operaciones posteriores de depuración o compilación. Si es necesario, puede agregar una referencia de ensamblado al archivo DLL.