Ecrire des extensions Node.js

Node.js est idéal pour écrire votre back-end en JavaScript. Mais que se passe-t-il si vous avez besoin de fonctionnalités qui ne sont pas fournies prêtes à l’emploi ou qui ne peuvent pas non plus être réalisées même avec des modules est disponible sous la forme d'une bibliothèque C / C ++? Eh bien, assez, vous pouvez écrire un addon qui vous permettra d’utiliser cette bibliothèque dans votre code JavaScript. Voyons comment.

Comme vous pouvez le lire dans la documentation de Node.js, les addons sont des objets partagés liés dynamiquement qui peuvent fournir un élément de liaison aux bibliothèques C / C ++. Cela signifie que vous pouvez utiliser pratiquement n'importe quelle bibliothèque C / C ++ et créer un addon qui vous permettra de l'utiliser dans Node.js.

Par exemple, nous allons créer un wrapper pour la norme std :: string objet.


Préparation

Avant de commencer à écrire, vous devez vous assurer que vous avez tout ce dont vous avez besoin pour compiler le module ultérieurement. Vous avez besoin node-gyp et toutes ses dépendances. Vous pouvez installer node-gyp en utilisant la commande suivante:

npm install -g node-gyp 

En ce qui concerne les dépendances, sur les systèmes Unix, vous aurez besoin de:

  • Python (2.7, 3.x ne fonctionnera pas)
  • faire
  • une chaîne d'outils de compilation C ++ (comme gpp ou g ++)

Par exemple, sur Ubuntu, vous pouvez installer tout cela en utilisant cette commande (Python 2.7 devrait déjà être installé):

sudo apt-get install build-essentials 

Sous Windows, vous aurez besoin de:

  • Python (2.7.3, 3.x ne fonctionnera pas)
  • Microsoft Visual Studio C ++ 2010 (pour Windows XP / Vista)
  • Microsoft Visual Studio C ++ 2012 pour le bureau Windows (Windows 7/8)

La version Express de Visual Studio fonctionne bien.


le binding.gyp Fichier

Ce fichier est utilisé par node-gyp générer des fichiers de construction appropriés pour votre addon. La totalité .gyps la documentation de fichier peut être trouvée sur leur page Wiki, mais pour nos besoins ce fichier simple fera:

"cibles": ["nom_cible": "stdstring", "sources": ["addon.cc", "stdstring.cc"]] 

le nom_cible peut être n'importe quel nom que vous aimez. le sources Le tableau contient tous les fichiers source utilisés par l'addon. Dans notre exemple, il y a addon.cc, qui contiendra le code nécessaire à la compilation de notre addon et stdstring.cc, qui contiendra notre classe wrapper.


le STDStringWrapper Classe

Nous commencerons par définir notre classe dans le stdstring.h fichier. Les deux premières lignes devraient vous être familières si vous avez déjà programmé en C++.

#ifndef STDSTRING_H #define STDSTRING_H 

C'est un garde standard. Ensuite, nous devons inclure ces deux en-têtes:

#comprendre  #comprendre 

Le premier est pour le std :: string la classe et la seconde incluent est pour tout ce qui concerne Node et V8.

Après cela, nous pouvons déclarer notre classe:

classe STDStringWrapper: noeud public :: ObjectWrap  

Pour toutes les classes que nous voulons inclure dans notre addon, nous devons étendre la node :: ObjectWrap classe.

Maintenant nous pouvons commencer à définir privé propriétés de notre classe:

 private: std :: string * s_; STDStringWrapper explicite (std :: string s = ""); ~ STDStringWrapper (); 

Outre le constructeur et le destructeur, nous définissons également un pointeur sur std :: string. C'est le cœur de la technique qui peut être utilisée pour coller des bibliothèques C / C ++ sur Node - nous définissons un pointeur privé sur la classe C / C ++ et nous opérons plus tard sur ce pointeur dans toutes les méthodes..

Ensuite, nous déclarons le constructeur propriété statique, qui contiendra la fonction qui créera notre classe dans la V8:

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

S'il vous plaît se référer à la v8 :: persistant modèle de documentation pour plus d'informations.

Maintenant, nous aurons aussi un Nouveau méthode, qui sera assignée à la constructeur ci-dessus, lorsque V8 initialise notre classe:

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

Chaque fonction de la V8 ressemblera à ceci: elle acceptera une référence à la v8 :: Arguments objet et retourne un v8 :: Handle> v8 :: Valeur> - C’est ainsi que V8 gère le JavaScript de type faible lorsque nous programmons en C fort++.

Après cela, nous aurons deux méthodes qui seront insérées dans le prototype de notre objet:

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

le toString () méthode nous permettra d'obtenir la valeur de s_ au lieu de [Objet Objet] quand on l'utilise avec des chaînes JavaScript normales.

Enfin, nous aurons la méthode d’initialisation (celle-ci sera appelée par la V8 pour attribuer le constructeur fonction) et nous pouvons fermer le garde d’inclusion:

public: static void Init (v8 :: Handle exportations); ; #fin si

le exportations objet est équivalent à la module.exports dans les modules JavaScript.


le stdstring.cc Fichier, constructeur et destructeur

Maintenant, créez le stdstring.cc fichier. Nous devons d'abord inclure notre en-tête:

#include "stdstring.h" 

Et définir le constructeur propriété (puisqu'il est statique):

v8 :: persistant STDStringWrapper :: constructor;

Le constructeur de notre classe va juste allouer le s_ propriété:

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

Et le destructeur sera effacer Pour éviter une fuite de mémoire:

STDStringWrapper :: ~ STDStringWrapper () delete s_;  

Également doit effacer tout ce que vous allouez avec Nouveau, chaque fois qu'il y a une chance qu'une exception soit levée, alors gardez cela à l'esprit ou utilisez des pointeurs partagés.


le Init Méthode

Cette méthode sera appelée par la V8 pour initialiser notre classe (attribuer le constructeur, mettre tout ce que nous voulons utiliser en JavaScript dans le exportations objet):

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

Nous devons d’abord créer un modèle de fonction pour notre Nouveau méthode:

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

C'est un peu comme nouvelle fonction en JavaScript - cela nous permet de préparer notre classe JavaScript.

Maintenant, nous pouvons définir le nom de cette fonction si nous le souhaitons (si vous omettez cela, votre constructeur sera anonyme, il aurait function nom_comme () contre une fonction () ):

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

Nous avons utilisé v8 :: String :: NewSymbol () qui crée un type spécial de chaîne utilisé pour les noms de propriétés - cela permet au moteur de gagner un peu de temps.

Après cela, nous définissons le nombre de champs que chaque instance de notre classe aura:

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

Nous avons deux méthodes - ajouter() et toString (), donc nous avons mis cela à 2.

Nous pouvons maintenant ajouter nos méthodes au prototype de la fonction:

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

Cela semble beaucoup de code, mais si vous regardez de plus près, vous verrez un motif: nous utilisons tpl-> PrototypeTemplate () -> Set () d'ajouter chacune des méthodes. Nous leur attribuons également un nom (en utilisant v8 :: String :: NewSymbol ()) et un FunctionTemplate.

Enfin, on peut mettre le constructeur dans le constructeur propriété de notre classe et dans le exportations objet:

 constructeur = v8 :: persistant:: New (tpl-> GetFunction ()); exports-> Set (v8 :: String :: NewSymbol ("STDString"), constructeur); 

le Nouveau Méthode

Nous allons maintenant définir la méthode qui agira comme un JavaScript Object.prototype.constructor:

v8 :: Poignée STDStringWrapper :: New (const v8 :: Arguments & args) 

Nous devons d'abord créer une portée pour cela:

 v8 :: scope de HandleScope; 

Après cela, nous pouvons utiliser le .IsConstructCall () méthode du args objet pour vérifier si la fonction constructeur a été appelée à l'aide du Nouveau mot-clé:

 if (args.IsConstructCall ())  

Si tel est le cas, convertissons d'abord l'argument passé en std :: string comme ça:

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

… Afin que nous puissions le transmettre au constructeur de notre classe wrapper:

 STDStringWrapper * obj = new STDStringWrapper (s); 

Après cela, nous pouvons utiliser le .Emballage() méthode de l'objet que nous avons créé (qui est hérité de node :: ObjectWrap) l'assigner à la ce variable:

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

Enfin, nous pouvons retourner l'objet nouvellement créé:

 retourne args.This (); 

Si la fonction n'a pas été appelée avec Nouveau, nous allons simplement invoquer le constructeur tel qu'il serait. Ensuite, créons une constante pour le nombre d'arguments:

  else const int argc = 1; 

Créons maintenant un tableau avec notre argument:

v8 :: Poignée STDStringWrapper :: add (const v8 :: Arguments & args) 

Et passer le résultat de la constructeur-> NewInstance méthode pour scope.Fermer, donc l'objet peut être utilisé plus tard (scope.Fermer permet fondamentalement de conserver le descripteur à un objet en le déplaçant vers la portée la plus haute (c’est ainsi que fonctionnent les fonctions):

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

le ajouter Méthode

Créons maintenant le ajouter méthode qui vous permettra d'ajouter quelque chose à l'interne std :: string de notre objet:

v8 :: Poignée STDStringWrapper :: add (const v8 :: Arguments & args) 

Nous devons d’abord créer une portée pour notre fonction et convertir l’argument en std :: string comme nous l'avons fait plus tôt:

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

Maintenant, nous devons déballer l'objet. C’est l’inverse du bouclage que nous avons fait plus tôt - cette fois nous allons obtenir le pointeur sur notre objet depuis le ce variable:

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

Ensuite, nous pouvons accéder à la s_ propriété et utiliser ses .ajouter() méthode:

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

Enfin, nous renvoyons la valeur actuelle de s_ propriété (encore une fois, en utilisant scope.Fermer):

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

Depuis le v8 :: String :: New () méthode accepte uniquement pointeur de char en tant que valeur, nous devons utiliser obj-> s _-> c_str () pour l'obtenir.


le toString Méthode

La dernière méthode nécessaire nous permettra de convertir l'objet en JavaScript Chaîne:

v8 :: Poignée STDStringWrapper :: toString (const v8 :: Arguments & args) 

C'est semblable au précédent, nous devons créer la portée:

 v8 :: scope de HandleScope; 

Déballer l'objet:

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

Et retourne le s_ propriété en tant que v8 :: String:

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

Bâtiment

La dernière chose à faire avant d’utiliser notre addon est bien sûr la compilation et la création de liens. Cela impliquera seulement deux commandes. Premier:

node-gyp configure 

Cela créera la configuration de construction appropriée pour votre système d'exploitation et votre processeur (Makefile sous UNIX et vcxproj sous Windows). Pour compiler et relier la bibliothèque, appelez simplement:

construction de noeud-gyp 

Si tout se passe bien, vous devriez voir quelque chose comme ceci dans votre console:

Et il devrait y avoir un construire répertoire créé dans le dossier de votre addon.

Essai

Maintenant nous pouvons tester notre addon. Créer un test.js fichier dans le dossier de votre addon et nécessite la bibliothèque compilée (vous pouvez omettre le .nœud extension):

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

Ensuite, créez une nouvelle instance de notre objet:

var test = new addon.STDString ('test'); 

Et faites quelque chose avec, comme l'ajouter ou le convertir en chaîne:

test.add ('!'); console.log ('contenu du test:% s', test); 

Cela devrait avoir comme résultat ce qui suit dans la console après l'avoir exécutée:

Conclusion

J'espère qu'après avoir lu ce tutoriel, vous ne penserez plus qu'il est difficile de créer, de construire et de tester des addons Node.js basés sur des bibliothèques C / C ++ personnalisées. En utilisant cette technique, vous pouvez facilement porter n'importe quelle bibliothèque C / C ++ vers Node.js. Si vous le souhaitez, vous pouvez ajouter plus de fonctionnalités à l'addon que nous avons créé. Il y a beaucoup de méthodes dans std :: string pour vous pratiquer avec.


Liens utiles

Consultez les ressources suivantes pour plus d'informations sur le développement des addons Node.js, la V8 et la bibliothèque de boucles d'événement C.

  • Documentation sur les additifs Node.js
  • Documentation V8
  • libuv (Bibliothèque de boucles d’événements C) sur GitHub