Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Node.js

Escribir complementos de Node.js

by
Difficulty:IntermediateLength:MediumLanguages:

Spanish (Español) translation by Steven (you can also view the original English article)

Node.js es excelente para escribir tu back-end en JavaScript. Pero, ¿qué sucede si necesitas alguna funcionalidad que no se proporciona de inmediato, o que tampoco se puede lograr incluso usando módulos, pero que sí está disponible en forma de una biblioteca C/C++? Bueno, sorprendentemente, puedes escribir un complemento que te permitirá usar esta biblioteca en tu código JavaScript. Vemos cómo.

Como puedes leer en la documentación de Node.js, los complementos son objetos compartidos vinculados dinámicamente que pueden proporcionar conexiones con las bibliotecas C/C++. Esto significa que puedes tomar prácticamente cualquier biblioteca C/C++ y crear un complemento que te permitirá usarlo en Node.js.

Como ejemplo, crearemos un contenedor para el objeto estándar std::string.


Preparación

Antes de comenzar a escribir, debes asegurarte de tener todo lo que necesitas para compilar el módulo más tarde. Necesita node-gyp y todas sus dependencias. Puedes instalar node-gyp usando el siguiente comando:

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

  • Python (2.7, 3.x no funcionará)
  • make
  • Un compilador de C++ (como gpp o g++)

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

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 escritorio de Windows (Windows 7/8)

La versión Express de Visual Studio funciona bien.


El archivo binding.gyp

Este archivo es utilizado por node-gyp para generar archivos de compilación apropiados para tu complemento. La documentación completa del archivo .gyp se puede encontrar en su página Wiki, pero para nuestros propósitos, este simple archivo servirá:

El target_name puede ser cualquier nombre que desees. La matriz sources contiene todos los archivos fuente que utiliza el complemento. En nuestro ejemplo, addon.cc contendrá el código necesario para compilar nuestro complemento y stdstring.cc va a tener nuestra clase contenedora.


La clase STDStringWrapper

Comenzaremos definiendo nuestra clase en el archivo stdstring.h. Las dos primeras líneas deberían resultarte familiares si alguna vez has programado en C++.

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

El primero es para la clase std::string y el segundo 'include' es para todas las cosas relacionadas con Node y V8.

Después de eso, podemos declarar nuestra clase:

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

Ahora podemos empezar a definir propiedades privadas de nuestra clase mediante private:

Además 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 conectar bibliotecas C/C++ con Node. Nosotros definimos un puntero privado a la clase C/C++ y luego operamos en ese puntero en todos los métodos.

Después, declaramos la propiedad estática constructor, que contendrá la función que creará nuestra clase en V8:

Consulta la documentación de la plantilla v8::Persistent para obtener más información.

Ahora también tendremos un método New, que será asignado al constructor anterior, cuando V8 inicialice nuestra clase:

Cada función para V8 se verá así: aceptará una referencia al objeto v8::Arguments y devolverá un v8::Handle>v8::Value> - así es como V8 trata con JavaScript de manera débil cuando programamos de manera fuerte en C++.

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

El método toString() nos permitirá obtener el valor de s_ en lugar de [Object object] cuando lo usemos con cadenas JavaScript normales.

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

El objeto exports es equivalente a module.exports en módulos de JavaScript.


El archivo stdstring.cc, constructor y destructor

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

Y definir la propiedad constructor (ya que es estática):

El constructor de nuestra clase solo asignará la propiedad s_:

Y el destructor lo eliminará mediante delete, para evitar una pérdida de memoria:

Además, debes eliminar todo lo que asignes con new, cada vez que exista la posibilidad de que se lance una excepción, así que ten esto en cuenta o usa punteros compartidos.


El método Init

Este método será llamado por V8 para inicializar nuestra clase (asigna el constructor, pone todo lo que queramos usar en JavaScript en el objeto exports):

Primero tenemos que crear una plantilla de función para nuestro método New:

Esta es una especie de función nueva en JavaScript como new Function y nos permite preparar nuestra clase de JavaScript.

Ahora podemos establecer el nombre de esta función si queremos (si omites esto, tu constructor será anónimo, tendría la función someName() {} en lugar de function() {}):

Usamos v8::String::NewSymbol() que crea un tipo especial de cadena que se usa para los nombres de las propiedades; esto le ahorra al motor un poco de tiempo.

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

Tenemos dos métodos: add() y toString(), así que lo configuramos en 2.

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

Esto parece mucho código, pero cuando miras de cerca, verás un patrón allí: usamos tpl->PrototypeTemplate()->Set() para agregar cada uno de los métodos. También le damos a cada uno de ellos un nombre (usando v8::String::NewSymbol()) y una FunctionTemplate.

Finalmente, podemos poner el constructor en la propiedad constructor de nuestra clase y en el objeto exports:


El método New

Ahora vamos a definir el método que se comportará como JavaScript Objeto.prototype.constructor:

Primero tenemos que crear un alcance para ello:

Después de eso, podemos usar el método .IsConstructCall() del objeto args para verificar si la función constructora fue llamada usando la palabra clave new:

Si es así, primero convierte el argumento pasado a std::string así:

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

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

Finalmente, podemos devolver el objeto recién creado:

Si la función no fue llamada usando new, simplemente invocaremos al constructor como sería. A continuación, creemos una constante para el recuento de argumentos:

Ahora creemos una matriz con nuestro argumento:

Y pasa el resultado del método constructor constructor->NewInstance a scope.Close, para que el objeto se pueda usar más tarde (scope.Close básicamente te permite preservar el identificador de un objeto moviéndolo al alcance superior; así es como trabajan las funciones ):


El método add

Ahora creemos el método add que te permitirá agregar algo al std::string interno de nuestro objeto:

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

Ahora tenemos que desenvolver el objeto. Esto es lo contrario del ajuste que hicimos anteriormente; esta vez obtendremos el puntero a nuestro objeto desde la variable this:

Luego podemos acceder a la propiedad s_ y usar su método .append():

Finalmente, devolvemos el valor actual de la propiedad s_ (nuevamente, usando scope.Close):

Dado que el método v8::String::New() acepta solo char pointer como valor, tenemos que usar obj->s_->c_str() para obtenerlo.


El método toString

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

Es similar al anterior, tenemos que crear el alcance:

Desenvuelve el objeto:

Y devuelve la propiedad s_ como v8::String:


Compilando

Lo último que debemos hacer antes de usar nuestro complemento es, por supuesto, compilar y vincular. Implicará solo dos comandos. Primero:

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

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

Y debería haber un directorio build creado en la carpeta de tu complemento.

Pruebas

Ahora podemos probar nuestro complemento. Crea un archivo test.js en la carpeta de tu complemento y requiere la biblioteca compilada (puedes omitir la extensión .node):

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

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

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 pienses que es difícil crear, compilar y probar complementos personalizados de Node.js basados en bibliotecas C/C++. Con esta técnica, puedes migrar casi cualquier biblioteca C/C++ a Node.js con facilidad. Si lo deseas, puedes agregar más funciones al complemento que creamos. Hay muchos métodos en std::string para que practiques.


Enlaces útiles

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.