Compartir a través de


Crear componentes de Windows en tiempo de ejecución en C++

En este artículo se muestra cómo usar C++ para crear un componente de Windows en tiempo de ejecución, que es un archivo DLL al que se puede llamar desde una aplicación de la Tienda Windows que se compila mediante JavaScript, o C#, Visual Basic o C++. A continuación se muestran varias razones para la compilación de un componente de ese tipo:

  • Aprovechar las ventajas de rendimiento proporcionadas por C++ en operaciones complejas o que necesitan gran cantidad de recursos de proceso.

  • Reutilizar código que ya está escrito y probado.

Cuando compilas una solución que contiene un proyecto de JavaScript o .NET y un proyecto de componente de Windows en tiempo de ejecución, los archivos de proyecto de JavaScript y el archivo DLL compilado se combinan en un paquete que puedes depurar localmente en el simulador o de forma remota en un dispositivo anclado a red. También puedes distribuir solo el proyecto de componente como SDK de extensión. Para obtener más información, consulta Cómo: Crear un kit de desarrollo de software.

Normalmente, cuando codificas tu componente de C++, usas la biblioteca de C++ y los tipos integrados normales salvo en el límite de la interfaz binaria abstracta (ABI) donde pasas datos al código y desde el código en otro paquete .winmd. Allí, has de utilizar tipos de Windows en tiempo de ejecución y la sintaxis especial que Visual C++ admite para crear y manipular esos tipos. Además, en el código de Visual C++, usarás tipos como delegate y event para implementar los eventos que se pueden desencadenar en tu componente y controlar en JavaScript, Visual Basic o C#. Para obtener más información acerca de la nueva sintaxis de Visual C++, consulta Referencia del lenguaje Visual C++ (C++/CX).

Reglas de grafía y nomenclatura

JavaScript

JavaScript distingue mayúsculas de minúsculas. Por consiguiente, debes seguir estas convenciones de grafía:

  • Al hacer referencia a los espacios de nombres y las clases de C++, debes utilizar la misma grafía que utilizas en el lado de C++.

  • Al llamar a métodos, utiliza el uso combinado de mayúsculas y minúsculas tipo Camel en el lado de C++. Por ejemplo, un método GetDate() de C++ se debe invocar desde JavaScript como getDate().

  • Un nombre de clase y un nombre de espacio de nombres activables no pueden contener caracteres Unicode.

.NET

Los lenguajes .NET siguen las reglas habituales de mayúsculas y minúsculas.

Crear instancias del objeto

Solo los tipos de Windows en tiempo de ejecución se pueden pasar a través del límite de ABI. El compilador producirá un error si el componente tiene un tipo como std::wstring como un parámetro o un tipo de valor devuelto en un método público. Los tipos integrados de Extensiones de componentes de Visual C++ (C++/CX) incluyen los valores escalares habituales como int y double, así como sus tipos equivalentes de definición de tipos int32, float64, etc. Para obtener más información, consulta Sistema de tipos (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();

Tipos integrados de C++, tipos de biblioteca y tipos de Windows en tiempo de ejecución

Una clase activable (también conocida como clase ref) es aquella de la que se pueden crear instancias desde otro lenguaje como JavaScript o Visual Basic. Para poder usarse desde otro lenguaje, un componente debe contener al menos una clase activable.

Un componente de Windows en tiempo de ejecución puede contener varias clases activables públicas, así como clases adicionales que solo el componente conoce internamente. Aplica el atributo [WebHostHidden] a los tipos de C++ que no van a estar visibles en 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 en Visual Basic) como ocurre con cualquier clase.

Una clase activable se debe declarar como public ref class sealed. La palabra clave de la clase ref indica al compilador que cree la clase como un tipo compatible de Windows en tiempo de ejecución y la palabra clave sealed especifica que la clase no puede heredarse. Windows en tiempo de ejecución no admite actualmente un modelo generalizado de herencia; un modelo limitado de herencia admite la creación de controles XAML personalizados. Para obtener más información, consulta Clases ref y structs ref (C++/CX).

En C++, todos los primitivos numéricos se definen en el espacio de nombres predeterminado. Platform Namespace contiene clases de C++ que son específicas del sistema de tipos de Windows en tiempo de ejecución. Entre ellas se incluyen Platform::String (Clase) y Platform::Object (Clase). Los tipos de colección concretos, como Platform::Collections::Map (Clase) y Platform::Collections::Vector (Clase), se definen en Platform::Collections (Espacio de nombres). Las interfaces públicas que estos tipos implementan se definen en Windows::Foundation::Collections (Espacio de nombres) (C++/CX). JavaScript, C# y Visual Basic consumen estos tipos de interfaz. Para obtener más información, consulta Sistema de tipos (C++/CX).

Hh441569.collapse_all(es-es,VS.120).gifMétodo que devuelve un valor de tipo integrado

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

Hh441569.collapse_all(es-es,VS.120).gifMétodo que devuelve un struct de valor personalizado

value struct es un objeto Plain Old Data (POD) que puede contener campos que son públicos de forma predeterminada. value struct se pasa por valor.

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

Para pasar structs de valor definidos por el usuario a través de la ABI, defina un objeto de JavaScript que tenga los mismos miembros que el struct de valor que se define en C++. Después, puedes pasar ese objeto como argumento al método de C++ para convertir implícitamente el objeto al tipo de 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);
}

Otro enfoque es definir una clase que implemente IPropertySet (no se muestra).

C#

En los lenguajes .NET, basta con crear una variable del tipo definido en el componente de 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();
}

Hh441569.collapse_all(es-es,VS.120).gifMétodos sobrecargados

Una clase ref pública de C++ puede contener métodos sobrecargados, pero JavaScript cuenta con 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 indicar la diferencia entre estas:

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

o estas:

En situaciones ambiguas, puedes asegurarte de que JavaScript siempre llame a una sobrecarga específica aplicando el atributo Windows::Foundation::Metadata::DefaultOverload a la firma de método en el archivo de encabezado.

Este código de 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 de .NET reconocen las sobrecargas en una clase ref de C++ igual que en cualquier clase de .NET Framework.

DateTime

En Windows en tiempo de ejecución, un objeto Windows::Foundation::DateTime es tan 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::DateTime. En su lugar, cada lenguaje proyecta el objeto DateTime de la manera que es nativa de ese lenguaje: el objeto Date en JavaScript y los tipos System.DateTime y System.DateTimeOffset en .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

Cuando pasas un valor DateTime de C++ a JavaScript, JavaScript lo acepta como un objeto Date y lo muestra de forma predeterminada como una cadena de fecha en 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;

}

.NET

Cuando un lenguaje de .NET pasa System.DateTime a un componente de C++, el método lo acepta como Windows::Foundation::DateTime. Cuando el componente pasa Windows::Foundation::DateTime a un método de .NET Framework, este 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 se pasan siempre a través del límite de ABI como identificadores a tipos de Windows en tiempo de ejecución, como Windows::Foundation::Collections::IVector^ y Windows::Foundation::Collections::IMap^. Por ejemplo, si devuelves un identificador a un objeto Platform::Collections::Map, se convierte implícitamente a Windows::Foundation::Collections::IMap^. Las interfaces de colección se definen en un espacio de nombres independiente de las clases de C++ que proporcionan implementaciones concretas. JavaScript y los lenguajes de .NET usan las interfaces. Para obtener más información, consulta Colecciones (C++/CX) y Array y WriteOnlyArray (C++/CX).

Hh441569.collapse_all(es-es,VS.120).gifPasar 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

Los lenguajes de .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();
            }
        }

Hh441569.collapse_all(es-es,VS.120).gifPasar 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

Los lenguajes de .NET ven IMap como 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 Extensiones de componentes de Visual C++ expone los miembros de datos públicos como propiedades, mediante la palabra clave property. El concepto es idéntico a las propiedades de .NET Framework. Una propiedad trivial se parece a un miembro de datos porque su funcionalidad está implícita. Una propiedad no trivial tiene descriptores de acceso get y set explícitos y una variable privada con nombre que es la “memoria auxiliar” para el valor. En este ejemplo, el miembro privado variable _propertyAValue es memoria auxiliar para PropertyA. Una propiedad puede desencadenar un evento cuando su valor cambia y una aplicación cliente puede registrarse para recibir ese evento.

C++


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


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

Los lenguajes de .NET tienen acceso a las propiedades de un objeto de C++ nativo como si fuera un objeto de .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;

}

Delegados y eventos

Un objeto delegate es un tipo de Windows en tiempo de ejecución que representa un objeto de función. Puedes utilizar delegados relacionados con eventos, devoluciones de llamada y llamadas de métodos asincrónicos para especificar que una acción se realice más adelante. Al igual que un objeto de función, el delegado proporciona seguridad de tipos que permite al compilador comprobar el tipo de valor devuelto y los tipos de parámetros de la función. La declaración de un delegado se parece a la firma de función, la implementación es similar a una definición de clase y la invocación se parece a una invocación de función.

Hh441569.collapse_all(es-es,VS.120).gifAgregar un agente de escucha de eventos

Puedes utilizar 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 utilizando los mecanismos estándar que se proporcionan en el lenguaje determinado.

C++

public:
    event SomeHandler^ someEvent;

En este ejemplo se usa el mismo código de C++ que en la sección anterior de propiedades.

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

En los lenguajes de .NET, la suscripción a un evento en un componente de C++ es igual que la suscripción a un evento en una clase de .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();
}

Hh441569.collapse_all(es-es,VS.120).gifAgregar varios agentes de escucha de eventos para un evento

JavaScript tiene un método addEventListener que permite que varios controladores se suscriban a un único 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

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 en tiempo de ejecución en C++ se declara con public class enum; es similar a una enumeración con ámbito en C++ estándar.

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

Los valores de enumeración se pasan entre C++ y JavaScript como enteros. Opcionalmente, puedes declarar un objeto de JavaScript que contenga los mismos valores con nombre que la enumeración de C++ y utilizarlo del modo siguiente.

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

Tanto C# como Visual Basic admiten enumeraciones. Estos lenguajes ven una clase de enumeración pública de C++ como una enumeración de .NET Framework.

Métodos asincrónicos

Para usar métodos asincrónicos expuestos por otros objetos de Windows en tiempo de ejecución, usa task (Clase) (Motor en tiempo de ejecución de simultaneidad). Para obtener más información, consulta Asychronous Programming in C++ y Paralelismo de tareas (Runtime de simultaneidad).

Para implementar métodos asincrónicos en C++, usa la función create_async que se define en ppltasks.h. Para obtener más información, consulta Crear operaciones asincrónicas en C++ para aplicaciones de la Tienda Windows. Para obtener un ejemplo, consulta Tutorial: Crear en C++ un componente básico de Windows en tiempo de ejecución y llamarlo desde JavaScript o C#. Los lenguajes de .NET usan los métodos asincrónicos de C++ como cualquier método asincrónico definido en .NET Framework.

Excepciones

Puedes producir cualquier tipo de excepción definido por Windows en tiempo de ejecución. No puedes derivar tipos personalizados de ningún tipo de excepción de Windows en tiempo de ejecución. Sin embargo, puedes producir una excepción COMException y proporcionar un HRESULT personalizado al que pueda tener acceso el código que detecta la excepción. No hay ninguna forma de especificar un mensaje personalizado en una excepción COMException.

Sugerencias de depuración

Al depurar una solución de JavaScript que tenga un objeto DLL de componente, puedes establecer el depurador para habilitar el recorrido paso a paso a través del script o el recorrido paso a paso a través del código nativo del componente, pero no ambos al mismo tiempo. Para cambiar la configuración, seleccione el nodo del proyecto de JavaScript en el Explorador de soluciones y después elija Propiedades, Depuración, Tipo de depurador.

Asegúrese de seleccionar las capacidades adecuadas en el diseñador de paquetes. Por ejemplo, si está intentando abrir un archivo de imagen en la Biblioteca de imágenes del usuario usando las API Windows en tiempo de ejecución, asegúrese de activar la casilla Biblioteca de imágenes en el panel Capacidades del diseñador de manifiestos.

Si tu código de JavaScript no parece ser reconocido por los métodos o propiedades públicos del componente, asegúrate de que en JavaScript estés utilizando el uso combinado de mayúsculas y minúsculas tipo Camel. Por ejemplo, en JavaScript debe hacerse referencia al método LogCalc de C++ como logCalc.

Si quitas de una solución un proyecto de componente de Windows en tiempo de ejecución de C++, también debes quitar manualmente la referencia al proyecto del proyecto de JavaScript. De lo contrario, se impiden las operaciones de depuración o compilación subsiguientes. Si es necesario, puedes agregar una referencia de ensamblado al archivo DLL.

Vea también

Conceptos

Tutorial: Crear en C++ un componente básico de Windows en tiempo de ejecución y llamarlo desde JavaScript o C#

Otros recursos

Roadmap for Windows Store apps using C++

Desarrollar el optimizador de recorridos de Mapas de Bing, una aplicación de la Tienda Windows en JavaScript y C++