Tutorial: Crear un Windows Runtime component básico en C++ y llamarlo desde JavaScript
Este tutorial muestra cómo crear un componente básico DLL de Windows en tiempo de ejecución que se pueda llamar desde JavaScript, C# o Visual Basic. Antes de iniciar este tutorial, asegúrate de familiarizarte con conceptos tales como la interfaz binaria abstracta (ABI), las clase ref y extensiones componentes de Visual C++ que facilitan el trabajo con las clases ref. Para obtener más información, consulta Crear componentes de Windows en tiempo de ejecución en C++ y Referencia del lenguaje Visual C++ (C++/CX).
Crear el proyecto de componente de C++
En este ejemplo, se crea en primer lugar el proyecto de componente, pero se podría crear antes el proyecto de JavaScript primero. El orden no importa.
Ten en cuenta que la clase principal del componente contiene ejemplos de definiciones de propiedades y métodos, así como una declaración de evento. Estos se proporcionan solo para mostrar cómo se hizo. No son necesarios y, en este ejemplo, vamos a reemplazar todo el código generado con nuestro propio código.
Para crear el proyecto de componente de C++
En la barra de menús de Visual Studio, elige Archivo, Nuevo, Proyecto.
En el cuadro de diálogo Nuevo proyecto, en el panel de la izquierda, expande Visual C++ y selecciona el nodo correspondiente a aplicaciones de la Tienda Windows.
En el panel central, selecciona Componente de Windows en tiempo de ejecución y, después, asigna al proyecto el nombre CppLib.
Elige el botón Aceptar.
Agregar una clase activable al componente
Una clase activable es una clase que puede crear JavaScript usando la expresión new. En el componente, se declara como public ref class sealed. De hecho, los archivos Class1.h y .cpp ya tienen una clase ref. Puedes cambiar el nombre, pero en este ejemplo usaremos el nombre predeterminado: Class1. Puedes definir clases adicionales ref en el componente, en caso de que sea necesario. Para obtener más información sobre las clases ref, consulta Sistema de tipos (C++/CX).
Agregar las instrucciones #include y using necesarias
Para agregar las instrucciones #include y using necesarias
Agrega estas directivas #include a Class1.h:
#include <collection.h> #include <amp.h> #include <amp_math.h>
Agrega estas directivas #include a Class1.cpp:
#include <ppltasks.h> #include <concurrent_vector.h>
Agrega estas instrucciones using a Class1.cpp:
using namespace concurrency; using namespace Platform::Collections; using namespace Windows::Foundation::Collections; using namespace Windows::Foundation; using namespace Windows::UI::Core;
Agregar a la clase un método público sincrónico
Los métodos que se ejecutan rápidamente se pueden implementar como métodos sincrónicos. Se ejecutan en el mismo subproceso que el llamador de JavaScript. Todos los tipos de parámetros y valores devueltos en métodos públicos deben ser tipos compatibles con Windows en tiempo de ejecución.
Para agregar a la clase un método público sincrónico
Agrega estas declaraciones a la clase en Class1.h:
public: Windows::Foundation::Collections::IVector<double>^ ComputeResult(double input); Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^ Class1::GetPrimesOrdered(int first, int last); Windows::Foundation::IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last); event PrimeFoundHandler^ primeFoundEvent; private: bool is_prime(int n); Windows::UI::Core::CoreDispatcher^ m_dispatcher; private: void ComputeResultImpl(concurrency::array_view<float, 1>&);
Agrega estas implementaciones a Class1.cpp:
void Class1::ComputeResultImpl(array_view<float, 1>& logs) { parallel_for_each( logs.extent, [=] (index<1> idx) restrict(amp) { logs[idx] = concurrency::fast_math::log10(logs[idx]); } ); } //Public API IVector<double>^ Class1::ComputeResult(double input) { // Implement your function in ISO C++ or // call into your C++ lib or DLL here. This example uses AMP. float numbers[] = { 1.0, 10.0, 60.0, 100.0, 600.0, 10000.0 }; array_view<float, 1> logs(6, numbers); ComputeResultImpl(logs); // Return a Windows Runtime-compatible type across the ABI auto res = ref new Vector<double>(); int len = safe_cast<int>(logs.extent.size()); for(int i = 0; i < len; i++) { res->Append(logs[i]); } return res; }
Agregar a la clase métodos públicos asincrónicos
Implementa los métodos que posiblemente tarden en ejecutarse como métodos asincrónicos. Un método asincrónico se ejecuta en un subproceso en segundo plano y no bloquea el subproceso de JavaScript. Crea el método público async usando el método create_async internamente. JavaScript llama al método como lo haría cualquier compromiso. Igual que con un método sincrónico, todos los tipos de parámetro deben ser tipos compatibles con Windows en tiempo de ejecución. En un método asincrónico, el tipo de valor devuelto debe ser uno de los siguientes:
IAsyncAction para los métodos asincrónicos que no devuelven ningún valor y no proporcionan información sobre el progreso.
IAsyncActionWithProgress para los métodos asincrónicos que no devuelven ningún valor, pero proporcionan información sobre el progreso.
IAsyncOperation<T> para los métodos asincrónicos que devuelven un valor T, pero no proporcionan información sobre el progreso.
IAsyncOperationWithProgress<T> para los métodos asincrónicos que devuelven un valor T y que también proporcionan información sobre el progreso.
Para obtener más información, consulta Crear operaciones asincrónicas en C++ para aplicaciones de la Tienda Windows.
Para agregar un método asincrónico que devuelva un resultado y proporcione información sobre el progreso
Agrega estas declaraciones a la clase en Class1.h:
Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^ Class1::GetPrimesOrdered(int first, int last); Windows::Foundation::IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last); event PrimeFoundHandler^ primeFoundEvent; private: bool is_prime(int n);
Agrega estas implementaciones a Class1.cpp:
// Determines whether the input value is prime. bool Class1::is_prime(int n) { if (n < 2) return false; for (int i = 2; i < n; ++i) { if ((n % i) == 0) return false; } return true; } // This method computes all primes, orders them, then returns the ordered results. IAsyncOperationWithProgress<IVector<int>^, double>^ Class1::GetPrimesOrdered(int first, int last) { return create_async([this, first, last] (progress_reporter<double> reporter) -> IVector<int>^ { // Ensure that the input values are in range. if (first < 0 || last < 0) { throw ref new InvalidArgumentException(); } // Perform the computation in parallel. concurrent_vector<int> primes; long operation = 0; long range = last - first + 1; double lastPercent = 0.0; parallel_for(first, last + 1, [this, &primes, &operation, range, &lastPercent, reporter](int n) { // Report progress message. double progress = 100.0 * InterlockedIncrement(&operation) / range; if (progress >= lastPercent) { reporter.report(progress); lastPercent += 1.0; } // If the value is prime, add it to the local vector. if (is_prime(n)) { primes.push_back(n); } }); // Sort the results. std::sort(begin(primes), end(primes), std::less<int>()); // Copy the results to an IVector object. The IVector // interface makes collections of data available to other // Windows Runtime components. IVector<int>^ results = ref new Vector<int>(); std::for_each(std::begin(primes), std::end(primes), [&results](int prime) { results->Append(prime); }); reporter.report(100.0); return results; }); }
Para agregar un método asincrónico que desencadene eventos y proporcione información sobre el progreso
Agrega estas declaraciones públicas a la clase en Class1.h:
Windows::Foundation::IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last); event PrimeFoundHandler^ primeFoundEvent;
Agrega esta declaración privada:
Windows::UI::Core::CoreDispatcher^ m_dispatcher;
Agrega el delegado siguiente en el ámbito del espacio de nombres en Class1.h:
public delegate void PrimeFoundHandler(int i);
Utilizamos este objeto para calcular los eventos desde la función paralela de nuevo al subproceso de la interfaz de usuario.
Agrega estas implementaciones a Class1.cpp:
// This method returns no value. Instead, it fires an event each time a prime is found, and transfers the prime through the event. IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last) { auto window = Windows::UI::Core::CoreWindow::GetForCurrentThread(); m_dispatcher = window->Dispatcher; return create_async([this, first, last](progress_reporter<double> reporter) { // Ensure that the input values are in range. if (first < 0 || last < 0) { throw ref new InvalidArgumentException(); } // Perform the computation in parallel. concurrent_vector<int> primes; long operation = 0; long range = last - first + 1; double lastPercent = 0.0; parallel_for(first, last + 1, [this, &primes, &operation, range, &lastPercent, reporter](int n) { // Report progress message. double progress = 100.0 * InterlockedIncrement(&operation) / range; if (progress >= lastPercent) { reporter.report(progress); lastPercent += 1.0; } // If the value is prime, add it to the local vector. if (is_prime(n)) { primes.push_back(n); m_dispatcher->RunAsync( CoreDispatcherPriority::Normal, ref new DispatchedHandler([this, n]() { this->primeFoundEvent(n); }, Platform::CallbackContext::Any)); } }); reporter.report(100.0); }); }
Crear un proyecto de JavaScript
Para crear un proyecto de JavaScript
En el Explorador de soluciones, en el menú contextual del nodo Solución, elige Agregar, Nuevo proyecto.
Expande JavaScript y elige Aplicación vacía.
Acepta el nombre predeterminado de App1; para ello elige el botón Aceptar.
Haz clic con el botón secundario en el nodo de proyecto App1 y elige Establecer como proyecto de inicio.
Agrega una referencia del proyecto a CppLib:
En el menú contextual del nodo Referencias, elige Agregar referencia.
En el panel izquierdo del cuadro de diálogo Administrador de referencias, selecciona Solución y, después, Proyectos.
En el panel central, selecciona CppLib y elige el botón Aceptar.
Agregar el HTML que invoque controladores de eventos de JavaScript
Pega este HTML en el nodo de <body> de la página default.html:
<div id="LogButtonDiv">
<button id="logButton" onclick="LogButton_Click()">Logarithms using AMP</button>
</div>
<div id="LogResultDiv">
<p id="logResult"></p>
</div>
<div id="OrderedPrimeButtonDiv">
<button id="orderedPrimeButton" onclick="ButtonOrdered_Click()">Primes using parallel_for with sort</button>
</div>
<div id="OrderedPrimeProgress">
<progress id="OrderedPrimesProgressBar" value="0" max="100"></progress>
</div>
<div id="OrderedPrimeResultDiv">
<p id="orderedPrimes">
Primes found (ordered):
</p>
</div>
<div id="UnorderedPrimeButtonDiv">
<button id="ButtonUnordered" onclick="ButtonUnordered_Click()">Primes returned as they are produced.</button>
</div>
<div id="UnorderedPrimeDiv">
<progress id="UnorderedPrimesProgressBar" value="0" max="100"></progress>
</div>
<div id="UnorderedPrime">
<p id="unorderedPrimes">
Primes found (unordered):
</p>
</div>
Agregar estilos
Quita el estilo de body y agrega estos estilos a default.css:
#LogButtonDiv { background: maroon; border: orange solid 1px; -ms-grid-row: 1; /* default is 1 */; -ms-grid-column: 1; /* default is 1 */ } #LogResultDiv { background: black; border: red solid 1px; -ms-grid-row: 1; -ms-grid-column: 2; } #UnorderedPrimeButtonDiv, #OrderedPrimeButtonDiv { background: green; border: orange solid 1px; -ms-grid-row: 2; -ms-grid-column:1; } #UnorderedPrimeDiv, #OrderedPrimeDiv { background: maroon; border: red solid 1px; -ms-grid-row: 2; -ms-grid-column:1; } #UnorderedPrimeProgress, #OrderedPrimeProgress { background: lightgray; border: red solid 1px; -ms-grid-row: 2; -ms-grid-column: 2; -ms-grid-column-span: 2; } #UnorderedPrimeResult, #OrderedPrimeResult { background: black; border: red solid 1px; -ms-grid-row: 2; -ms-grid-column: 3; }
Agregar controladores de eventos de JavaScript que llamen al DLL de componente
Agrega las siguientes funciones al final del archivo default.js: Se llama a estas funciones cuando se eligen los botones de la página principal. Observa cómo JavaScript genera clases de C++ y después llama a sus métodos y usa los valores devueltos para rellenar las etiquetas HTML.
var nativeObject = new cpplib.Class1(); function LogButton_Click() { var val = nativeObject.computeResult(0); var result = ""; for (i = 0; i < val.length; i++) { result += val[i] + "<br/>"; } document.getElementById('logResult').innerHTML = result; } function ButtonOrdered_Click() { document.getElementById('orderedPrimes').innerHTML = "Primes found (ordered): "; var asyncResult = nativeObject.getPrimesOrdered(2, 1000).then( function (v) { for (var i = 0; i < v.length; i++) document.getElementById('orderedPrimes').innerHTML += v[i] + " "; }, function (error) { document.getElementById('orderedPrimes').innerHTML += " " + error.description; }, function (p) { var progressBar = document.getElementById( "OrderedPrimesProgressBar"); progressBar.value = p; }); } function ButtonUnordered_Click() { document.getElementById('unorderedPrimes').innerHTML = "Primes found (unordered): "; nativeObject.onprimefoundevent = handler_unordered; var asyncResult = nativeObject.getPrimesUnordered(2, 1000).then( function () { }, function (error) { document.getElementById("unorderedPrimes").innerHTML += " " + error.description; }, function (p) { var progressBar = document.getElementById("UnorderedPrimesProgressBar"); progressBar.value = p; }); } var handler_unordered = function (n) { document.getElementById('unorderedPrimes').innerHTML += n.target.toString() + " "; };
Ejecutar la aplicación
Presiona F5.
Inspeccionar el componente en el Examinador de objetos (opcional)
En el Examinador de objetos, puedes inspeccionar todos los tipos de Windows en tiempo de ejecución que se definen en los archivos .winmd. Esto incluye los tipos en el espacio de nombres Platform y en el espacio de nombres predeterminado. Sin embargo, los tipos en el espacio de nombres Platform::Collections se definen en el archivo de encabezado collections.h y no en un archivo winmd. Por consiguiente, estos tipos no aparecen en el Examinador de objetos.
Para inspeccionar el componente en el Examinador de objetos
En la barra de menús de Visual Studio, elige Ver, Otras ventanas, Examinador de objetos.
En el panel izquierdo del Examinador de objetos, expande el nodo de CppLib para mostrar los tipos y métodos que se definen en el componente.
Sugerencias de depuración
Para mejorar la experiencia al usar la depuración, descarga los símbolos de depuración de los servidores de símbolos públicos de Microsoft. En el menú principal, elige Herramientas y, a continuación, selecciona Opciones. En la ventana Opciones, expande Depuración y selecciona Símbolos. Activa la casilla situada junto a Servidores de símbolos de Microsoft y elige Aceptar. Es posible que la primera vez se tarde algo de tiempo en la descarga. Para acelerar el rendimiento la próxima vez que presiones F5, usa el espacio proporcionado para especificar un directorio local donde almacenar los símbolos en memoria caché.
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 el valor, en el menú contextual del nodo del proyecto de JavaScript, en el Explorador de soluciones, elige Propiedades, Depuración, Tipo de depurador.
Asegúrese de seleccionar las capacidades adecuadas en el diseñador de paquetes. Por ejemplo, si estás intentando abrir un archivo con las API de Windows en tiempo de ejecución, asegúrate de activar la casilla Acceso a la biblioteca de documentos en el panel Capacidades del diseñador de paquetes.
Si el código de JavaScript no parece ser reconocido por los métodos o propiedades públicos del componente, asegúrate de que en JavaScript se esté usando el uso combinado de mayúsculas y minúsculas tipo Camel. Por ejemplo, en JavaScript debe hacerse referencia al método ComputeResult de C++ como computeResult.
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 desde el proyecto de JavaScript. De lo contrario, se impedirán las operaciones de depuración o compilación subsiguientes. Si es necesario, puedes agregar una referencia de ensamblado al archivo DLL.
Vea también
Referencia
Roadmap for Windows Store apps using C++