Escribiendo Node.js Addons

Node.js es ideal para escribir tu back-end en JavaScript. Pero, ¿qué sucede si necesita alguna funcionalidad que no se proporciona de forma inmediata o que tampoco se puede lograr incluso utilizando módulos, pero es Disponible en la forma de una biblioteca C / C ++? Bueno, lo suficientemente impresionante, puedes escribir un complemento que te permita usar esta biblioteca en tu código JavaScript. Veamos como.

Como puede leer en la documentación de Node.js, los complementos son objetos compartidos dinámicamente vinculados que pueden proporcionar pegamento a las bibliotecas de C / C ++. Esto significa que puede tomar casi cualquier biblioteca de C / C ++ y crear un complemento que le permita usarlo en Node.js.

Como ejemplo, crearemos una envoltura para el estándar std :: string objeto.


Preparación

Antes de comenzar a escribir, debe asegurarse de tener todo lo que necesita para compilar el módulo más adelante. Necesitas nodo-gyp y todas sus dependencias. Puedes instalar nodo-gyp usando el siguiente comando:

npm install -g node-gyp 

En cuanto a las dependencias, en sistemas Unix necesitarás:

  • Python (2.7, 3.x no funcionará)
  • hacer
  • una cadena de herramientas del compilador de C ++ (como gpp o g ++)

Por ejemplo, en Ubuntu puede instalar todo esto usando este comando (Python 2.7 ya debería estar instalado):

sudo apt-get install build-essentials 

En Windows necesitarás:

  • Python (2.7.3, 3.x no funcionará)
  • Microsoft Visual Studio C ++ 2010 (para Windows XP / Vista)
  • Microsoft Visual Studio C ++ 2012 para Windows Desktop (Windows 7/8)

La versión Express de Visual Studio funciona bien.


los binding.gyp Expediente

Este archivo es utilizado por nodo-gyp para generar archivos de compilación apropiados para su complemento. El conjunto .estafa La documentación del archivo se puede encontrar en su página de Wiki, pero para nuestros propósitos, este simple archivo lo hará:

"target": ["target_name": "stdstring", "sources": ["addon.cc", "stdstring.cc"]] 

los target_name Puede ser cualquier nombre que te guste. los fuentes array contiene todos los archivos de origen que utiliza el complemento. En nuestro ejemplo, hay addon.cc, que contendrá el código que se requiere para compilar nuestro complemento y stdstring.cc, que contendrá nuestra clase de envoltorio.


los STDStringWrapper Clase

Comenzaremos por definir nuestra clase en el stdstring.h expediente. Las primeras dos líneas deben ser familiares para usted si alguna vez ha programado en C++.

#ifndef STDSTRING_H #define STDSTRING_H 

Este es un estándar de incluir guardia. A continuación, tenemos que incluir estos dos encabezados:

#incluir  #incluir 

El primero es para el std :: string clase y el segundo incluye es para todo lo relacionado con Node y V8.

Después de eso, podemos declarar nuestra clase:

clase STDStringWrapper: public node :: ObjectWrap  

Para todas las clases que queremos incluir en nuestro complemento, debemos extender el nodo :: ObjectWrap clase.

Ahora podemos empezar a definir privado propiedades de nuestra clase:

 privado: std :: string * s_; STDStringWrapper explícito (std :: string s = ""); ~ STDStringWrapper (); 

Aparte del constructor y el destructor, también definimos un puntero a std :: string. Este es el núcleo de la técnica que se puede usar para pegar las bibliotecas de C / C ++ en el nodo: definimos un puntero privado a la clase C / C ++ y luego operamos con ese puntero en todos los métodos.

A continuación declaramos el constructor propiedad estática, que contendrá la función que creará nuestra clase en V8:

 static v8 :: Handle Nuevo (const v8 :: Arguments & args); 

por favor refiérase a v8 :: Persistente Plantilla de documentación para más información..

Ahora también tendremos un Nuevo Método, que será asignado a la constructor arriba, cuando V8 inicializa nuestra clase:

 static v8 :: Handle New (const v8 :: Arguments & args); 

Cada función para V8 se verá así: aceptará una referencia a la v8 :: Argumentos objetar y devolver un v8 :: Handle> v8 :: Value> - así es como V8 maneja el JavaScript de tipo débil cuando programamos en C de tipo fuerte++.

Después de eso, tendremos dos métodos que se insertarán en el prototipo de nuestro objeto:

 static v8 :: Handle add (const v8 :: Arguments & args); static v8 :: Handle toString (const v8 :: Arguments & args);

los Encadenar() método nos permitirá obtener el valor de s_ en lugar de [Objeto Objeto] cuando lo usamos con cadenas normales de JavaScript.

Finalmente, tendremos el método de inicialización (esto será llamado por V8 para asignar el constructor Función) y podemos cerrar la guarda de inclusión:

public: static void Init (v8 :: Handle exportaciones); ; #terminara si

los las exportaciones objeto es equivalente a la módulo.exportaciones en los módulos de JavaScript.


los stdstring.cc Archivo, Constructor y Destructor

Ahora crea el stdstring.cc expediente. Primero tenemos que incluir nuestro encabezado:

#include "stdstring.h" 

Y definir el constructor Propiedad (ya que es estática):

v8 :: Persistente STDStringWrapper :: constructor;

El constructor de nuestra clase simplemente asignará el s_ propiedad:

STDStringWrapper :: STDStringWrapper (std :: string s) s_ = new std :: string (s);  

Y el destructor lo hará. borrar Para evitar una pérdida de memoria:

STDStringWrapper :: ~ STDStringWrapper () delete s_;  

También tú debe borrar todo lo que asignas con nuevo, Cada vez que hay una posibilidad de que se produzca una excepción, así que tenlo en cuenta o utiliza punteros compartidos..


los En eso Método

Este método será llamado por V8 para inicializar nuestra clase (asigne el constructor, Pon todo lo que queramos usar en JavaScript en el las exportaciones objeto):

void STDStringWrapper :: Init (v8 :: Handle exportaciones) 

Primero tenemos que crear una plantilla de función para nuestra Nuevo método:

v8 :: Local tpl = v8 :: FunctionTemplate :: New (New);

Esto es algo así como nueva función en JavaScript - nos permite preparar nuestra clase de JavaScript.

Ahora podemos configurar el nombre de esta función si queremos (si omite esto, su constructor será anónimo, tendría función someName () versus función () ):

tpl-> SetClassName (v8 :: String :: NewSymbol ("STDString"));

Nosotros usamos v8 :: String :: NewSymbol () que crea un tipo especial de cadena utilizada para los nombres de propiedades, lo que ahorra al motor un poco de tiempo.

Después de eso, establecemos cuántos campos tendrá cada instancia de nuestra clase:

tpl-> InstanceTemplate () -> SetInternalFieldCount (2);

Tenemos dos metodos - añadir() y Encadenar(), así que configuramos esto para 2.

Ahora podemos agregar nuestros métodos al prototipo de la función:

tpl-> PrototypeTemplate () -> Set (v8 :: String :: NewSymbol ("add"), v8 :: FunctionTemplate :: New (add) -> GetFunction ()); tpl-> PrototypeTemplate () -> Set (v8 :: String :: NewSymbol ("toString"), v8 :: FunctionTemplate :: New (toString) -> GetFunction ());

Esto parece un montón de código, pero cuando lo miras de cerca, verás un patrón allí: usamos tpl-> PrototypeTemplate () -> Set () Añadir cada uno de los métodos. También les damos un nombre a cada uno de ellos v8 :: String :: NewSymbol ()) y un Plantilla de funciones.

Finalmente, podemos poner al constructor en el constructor propiedad de nuestra clase y en el las exportaciones objeto:

 constructor = v8 :: Persistente:: Nuevo (tpl-> GetFunction ()); exportaciones-> Set (v8 :: String :: NewSymbol ("STDString"), constructor); 

los Nuevo Método

Ahora definiremos el método que actuará como JavaScript. Object.prototype.constructor:

v8 :: Mango STDStringWrapper :: Nuevo (const v8 :: Argumentos y argumentos) 

Primero tenemos que crear un ámbito para ello:

 v8 :: alcance de HandleScope; 

Después de eso, podemos usar el .IsConstructCall () método de la args objeto para comprobar si la función del constructor fue llamada utilizando el nuevo palabra clave:

 if (args.IsConstructCall ())  

Si es así, primero vamos a convertir el argumento pasado a std :: string Me gusta esto:

 v8 :: String :: Utf8Value str (args [0] -> ToString ()); std :: string s (* str);

... para que podamos pasarlo al constructor de nuestra clase de envoltura:

 STDStringWrapper * obj = nuevo STDStringWrapper (s); 

Después de eso, podemos usar el .Envolver() método del objeto que creamos (que se hereda de nodo :: ObjectWrap) para asignarlo a la esta variable:

 obj-> Envolver (args.This ()); 

Finalmente, podemos devolver el objeto recién creado:

 devuelve args.This (); 

Si la función no fue llamada usando nuevo, Simplemente invocaremos al constructor como sería. A continuación, vamos a crear una constante para el conteo de argumentos:

  else const int argc = 1; 

Ahora vamos a crear una matriz con nuestro argumento:

v8 :: Mango STDStringWrapper :: add (const v8 :: Arguments & args) 

Y pasar el resultado de la constructor-> NewInstance método para alcance.cerrar, para que el objeto pueda ser utilizado más tarde (alcance.cerrar básicamente, le permite conservar el identificador de un objeto moviéndolo al ámbito superior, así es como funcionan las funciones):

 return scope.Close (constructor-> NewInstance (argc, argv));  

los añadir Método

Ahora vamos a crear el añadir Método que le permitirá agregar algo a lo interno. std :: string de nuestro objeto:

v8 :: Mango STDStringWrapper :: add (const v8 :: Arguments & args) 

Primero tenemos que crear un alcance para nuestra función y convertir el argumento a std :: string como hicimos antes:

 v8 :: alcance de HandleScope; v8 :: String :: Utf8Value str (args [0] -> ToString ()); std :: string s (* str); 

Ahora tenemos que desenvolver el objeto. Esto es lo contrario de la envoltura que hicimos anteriormente - esta vez obtendremos el puntero a nuestro objeto desde el esta variable:

STDStringWrapper * obj = ObjectWrap :: Unwrap(args.This ());

Entonces podemos acceder a la s_ propiedad y uso de su .adjuntar() método:

 obj-> s _-> append (s); 

Finalmente, devolvemos el valor actual de la s_ propiedad (de nuevo, usando alcance.cerrar):

 return scope.Close (v8 :: String :: New (obj-> s _-> c_str ()));  

Desde el v8 :: String :: Nuevo () el método acepta solo puntero de char Como valor, tenemos que usar obj-> s _-> c_str () para conseguirlo.


los Encadenar Método

El último método necesario nos permitirá convertir el objeto a JavaScript Cuerda:

v8 :: Mango STDStringWrapper :: toString (const v8 :: Arguments & args) 

Es similar al anterior, tenemos que crear el alcance:

 v8 :: alcance de HandleScope; 

Desenvuelve el objeto:

STDStringWrapper * obj = ObjectWrap :: Unwrap(args.This ()); 

Y devolver el s_ propiedad como v8 :: Cuerda:

 return scope.Close (v8 :: String :: New (obj-> s _-> c_str ()));  

edificio

Lo último que hay que hacer antes de usar nuestro complemento es, por supuesto, la compilación y la vinculación. Implicará sólo dos comandos. Primero:

Configuración de nodo-gyp 

Esto creará la configuración de compilación adecuada para su sistema operativo y procesador (Makefile en UNIX y vcxproj en Windows). Para compilar y vincular la biblioteca, simplemente llame:

construcción node-gyp 

Si todo va bien, deberías ver algo como esto en tu consola:

Y debería haber una construir directorio creado en la carpeta de tu addon.

Pruebas

Ahora podemos probar nuestro complemento. Crear un test.js en la carpeta de su complemento y solicite la biblioteca compilada (puede omitir la .nodo extensión):

var addon = require ('./ build / Release / addon'); 

A continuación, crea una nueva instancia de nuestro objeto:

var prueba = nuevo addon.STDString ('prueba'); 

Y haz algo con él, como agregarlo o convertirlo en una cadena:

test.add ('!'); console.log ('contenido de la prueba:% s', prueba); 

Esto debería resultar en algo como lo siguiente en la consola, después de ejecutarlo:

Conclusión

Espero que después de leer este tutorial ya no piense que es difícil crear, construir y probar los complementos de Node.js basados ​​en la biblioteca C / C ++ personalizados. Usando esta técnica, puede trasladar casi cualquier biblioteca C / C ++ a Node.js con facilidad. Si lo desea, puede agregar más características al complemento que creamos. Hay un montón de métodos en std :: string para que practiques con.


Enlaces útiles

Consulte los siguientes recursos para obtener más información sobre el desarrollo de complementos Node.js, V8 y la biblioteca de bucles de eventos C.

  • Documentación de los complementos de Node.js
  • Documentacion v8
  • libuv (Biblioteca de bucles de eventos C) en GitHub