Scrivere i componenti aggiuntivi di Node.js

Node.js è ottimo per scrivere il tuo back-end in JavaScript. Ma cosa succede se hai bisogno di alcune funzionalità che non sono fornite fuori dalla scatola, o che non possono essere realizzate anche usando i moduli, ma è disponibile sotto forma di libreria C / C ++? Beh, incredibilmente, puoi scrivere un addon che ti permetterà di usare questa libreria nel tuo codice JavaScript. Vediamo come.

Come puoi leggere nella documentazione di Node.js, gli addon sono oggetti condivisi dinamicamente collegati che possono fornire colla alle librerie C / C ++. Ciò significa che puoi prendere praticamente qualsiasi libreria C / C ++ e creare un addon che ti permetta di usarlo in Node.js.

Ad esempio, creeremo un wrapper per lo standard std :: string oggetto.


Preparazione

Prima di iniziare a scrivere, devi assicurarti di avere tutto il necessario per compilare il modulo in un secondo momento. Hai bisogno node-gyp e tutte le sue dipendenze. Puoi installare node-gyp usando il seguente comando:

npm install -g node-gyp 

Per quanto riguarda le dipendenze, sui sistemi Unix avrete bisogno di:

  • Python (2.7, 3.x non funzionerà)
  • rendere
  • una toolchain del compilatore C ++ (come gpp o g ++)

Ad esempio, su Ubuntu puoi installare tutto questo usando questo comando (Python 2.7 dovrebbe essere già installato):

sudo apt-get install build-essentials 

Su Windows avrai bisogno di:

  • Python (2.7.3, 3.x non funzionerà)
  • Microsoft Visual Studio C ++ 2010 (per Windows XP / Vista)
  • Microsoft Visual Studio C ++ 2012 per Windows Desktop (Windows 7/8)

La versione Express di Visual Studio funziona correttamente.


Il binding.gyp File

Questo file è usato da node-gyp per generare file di build appropriati per il tuo addon. Il tutto .gyp la documentazione del file può essere trovata sulla loro pagina Wiki, ma per i nostri scopi questo semplice file farà:

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

Il nome_destinazione può essere un nome che ti piace Il fonti la matrice contiene tutti i file sorgente utilizzati dall'addon. Nel nostro esempio, c'è addon.cc, che conterrà il codice necessario per compilare il nostro addon e stdstring.cc, che conterrà la nostra classe wrapper.


Il STDStringWrapper Classe

Inizieremo definendo la nostra classe nel stdstring.h file. Le prime due linee dovrebbero esserti familiari se hai mai programmato in C++.

#ifndef STDSTRING_H #define STDSTRING_H 

Questo è uno standard include guardia. Successivamente, dobbiamo includere queste due intestazioni:

#includere  #includere 

Il primo è per il std :: string classe e la seconda include per tutte le cose relative a Node e V8.

Dopo di ciò, possiamo dichiarare la nostra classe:

class STDStringWrapper: public node :: ObjectWrap  

Per tutte le classi che vogliamo includere nel nostro addon, dobbiamo estendere il nodo :: ObjectWrap classe.

Ora possiamo iniziare a definire privato proprietà della nostra classe:

 privato: std :: string * s_; esplicito STDStringWrapper (std :: string s = ""); ~ STDStringWrapper (); 

Oltre al costruttore e al distruttore, definiamo anche un puntatore a std :: string. Questo è il nucleo della tecnica che può essere usata per incollare librerie C / C ++ su Node - definiamo un puntatore privato alla classe C / C ++ e poi operiamo su quel puntatore in tutti i metodi.

Quindi dichiariamo il costruttore proprietà statica, che manterrà la funzione che creerà la nostra classe in V8:

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

Si prega di fare riferimento al v8 :: Persistent documentazione del modello per ulteriori informazioni.

Ora avremo anche un Nuovo metodo, che sarà assegnato al costruttore sopra, quando V8 inizializza la nostra classe:

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

Ogni funzione per V8 sarà simile a questa: accetterà un riferimento a v8 :: Argomenti oggetto e ritorno a v8 :: Handle> V8 :: Valore> - questo è il modo in cui V8 gestisce JavaScript con caratteri deboli quando programmiamo in C con caratteri forti++.

Dopodiché avremo due metodi che verranno inseriti nel prototipo del nostro oggetto:

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

Il accordare() il metodo ci permetterà di ottenere il valore di S_ invece di [Oggetto Oggetto] quando lo usiamo con normali stringhe JavaScript.

Infine, avremo il metodo di inizializzazione (questo sarà chiamato da V8 per assegnare il costruttore funzione) e possiamo chiudere la guardia include:

public: static void Init (v8 :: Handle esportazioni); ; #finisci se

Il esportazioni oggetto è equivalente al module.exports nei moduli JavaScript.


Il stdstring.cc File, costruttore e distruttore

Ora crea il stdstring.cc file. Per prima cosa dobbiamo includere la nostra intestazione:

#include "stdstring.h" 

E definire il costruttore proprietà (poiché è statica):

v8 :: Persistent STDStringWrapper :: costruttore;

Il costruttore della nostra classe assegnerà solo il S_ proprietà:

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

E il distruttore lo farà Elimina esso, per evitare una perdita di memoria:

STDStringWrapper :: ~ STDStringWrapper () cancella s_;  

Anche tu dovere Elimina tutto ciò che si assegna nuovo, ogni volta c'è la possibilità che venga lanciata un'eccezione, quindi tienilo a mente o usa i puntatori condivisi.


Il Dentro Metodo

Questo metodo verrà chiamato da V8 per inizializzare la nostra classe (assegnare il costruttore, metti tutto quello che vogliamo usare in JavaScript nel esportazioni oggetto):

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

Per prima cosa dobbiamo creare un modello di funzione per il nostro Nuovo metodo:

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

Questo è un po 'come nuova funzione in JavaScript: ci consente di preparare la nostra classe JavaScript.

Ora possiamo impostare il nome di questa funzione se vogliamo (se lo ometti, il tuo costruttore sarà anonimo, lo sarebbe function someName () contro funzione () ):

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

Abbiamo usato v8 :: :: String NewSymbol () che crea un tipo speciale di stringa usata per i nomi delle proprietà - questo fa risparmiare al motore un po 'di tempo.

Dopodiché, stabiliamo quanti campi avranno ciascuna istanza della nostra classe:

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

Abbiamo due metodi - Inserisci() e accordare(), così abbiamo impostato questo 2.

Ora possiamo aggiungere i nostri metodi al prototipo della funzione:

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

Questo sembra un sacco di codice, ma quando guardi da vicino, vedrai un modello lì: usiamo tpl-> PrototypeTemplate () -> Set () aggiungere ciascuno dei metodi. Diamo anche a ciascuno di loro un nome (usando v8 :: :: String NewSymbol ()) e a FunctionTemplate.

Infine, possiamo mettere il costruttore nel costruttore proprietà della nostra classe e nel esportazioni oggetto:

 constructor = v8 :: Persistente:: Nuovo (tpl-> GetFunction ()); exports-> Set (v8 :: String :: NewSymbol ("STDString"), costruttore); 

Il Nuovo Metodo

Ora definiremo il metodo che fungerà da JavaScript Object.prototype.constructor:

v8 :: Handle STDStringWrapper :: New (const v8 :: Arguments & args) 

Per prima cosa dobbiamo creare un ambito per questo:

 v8 :: ambito di HandleScope; 

Dopo ciò, possiamo usare il .IsConstructCall () metodo del args oggetto per verificare se la funzione di costruzione è stata chiamata usando il nuovo parola chiave:

 if (args.IsConstructCall ())  

Se è così, prima convertiamo l'argomento passato a std :: string come questo:

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

... in modo che possiamo passarlo al costruttore della nostra classe wrapper:

 STDStringWrapper * obj = new STDStringWrapper (s); 

Dopo ciò, possiamo usare il .Wrap () metodo dell'oggetto che abbiamo creato (da cui è ereditato nodo :: ObjectWrap) per assegnarlo al Questo variabile:

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

Infine, possiamo restituire l'oggetto appena creato:

 return args.Questo (); 

Se la funzione non è stata chiamata usando nuovo, invocheremo semplicemente il costruttore come sarebbe. Quindi, creiamo una costante per il conteggio degli argomenti:

  else const int argc = 1; 

Ora creiamo un array con il nostro argomento:

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

E passare il risultato del constructor-> NewInstance metodo a scope.Close, quindi l'oggetto può essere usato successivamente (scope.Close fondamentalmente ti permette di preservare l'handle di un oggetto spostandolo allo scope più alto - questo è il modo in cui funzionano le funzioni):

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

Il Inserisci Metodo

Ora creiamo il Inserisci metodo che ti consentirà di aggiungere qualcosa all'interno std :: string del nostro oggetto:

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

Per prima cosa dobbiamo creare uno scope per la nostra funzione e convertire l'argomento in std :: string come abbiamo fatto prima:

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

Ora dobbiamo scartare l'oggetto. Questo è il rovescio del wrapping che abbiamo fatto prima - questa volta otterremo il puntatore al nostro oggetto dal Questo variabile:

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

Quindi possiamo accedere a S_ proprietà e usa il suo .aggiungere() metodo:

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

Infine, restituiamo il valore corrente di S_ proprietà (di nuovo, usando scope.Close):

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

Dal momento che il v8 :: String :: Nuovo () metodo accetta solo puntatore char come valore, dobbiamo usare obj-> s _-> c_str () capirlo.


Il accordare Metodo

L'ultimo metodo necessario ci consentirà di convertire l'oggetto in JavaScript Stringa:

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

È simile al precedente, dobbiamo creare l'ambito:

 v8 :: ambito di HandleScope; 

Scollega l'oggetto:

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

E restituire il S_ proprietà come a v8 :: String:

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

Costruzione

L'ultima cosa da fare prima di usare il nostro addon, è ovviamente la compilazione e il collegamento. Coinvolgerà solo due comandi. Primo:

configurazione di node-gyp 

Questo creerà la configurazione di build appropriata per il tuo SO e processore (Makefile su UNIX e vcxproj Su Windows). Per compilare e collegare la libreria, basta chiamare:

build node-gyp 

Se tutto va bene, dovresti vedere qualcosa di simile nella tua console:

E dovrebbe esserci un costruire directory creata nella cartella del tuo addon.

analisi

Ora possiamo testare il nostro addon. Creare un test.js file nella cartella del tuo addon e richiedi la libreria compilata (puoi omettere il .nodo estensione):

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

Quindi, crea una nuova istanza del nostro oggetto:

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

E fai qualcosa con esso, come aggiungerlo o convertirlo in una stringa:

test.add ( '!'); console.log ('test \' s contents:% s ', test); 

Questo dovrebbe risultare in qualcosa di simile alla console, dopo averlo eseguito:

Conclusione

Spero che dopo aver letto questo tutorial non penserete più che è difficile creare, costruire e testare componenti aggiuntivi basati su librerie C / C ++, Node.js. Usando questa tecnica è possibile trasferire praticamente qualsiasi libreria C / C ++ su Node.js. Se lo desideri, puoi aggiungere ulteriori funzionalità al componente aggiuntivo che abbiamo creato. Ci sono molti metodi in std :: string per far pratica con.


link utili

Controlla le seguenti risorse per ulteriori informazioni sullo sviluppo di addon Node.js, V8 e la libreria del ciclo di eventi C.

  • Documentazione sui componenti aggiuntivi di Node.js
  • Documentazione V8
  • libuv (Libreria del ciclo degli eventi C) su GitHub