Scrierea Node.js Addons

Node.js este excelent pentru scrierea back-end-ului în JavaScript. Dar dacă aveți nevoie de anumite funcții care nu sunt furnizate din cutie sau care nu pot fi realizate chiar folosind module, este disponibil sub forma unei biblioteci C / C ++? Destul de grozav, puteți scrie un addon care vă va permite să utilizați această bibliotecă în codul dvs. JavaScript. Să vedem cum.

După cum puteți citi în documentația Node.js, addon-urile sunt obiecte comune partajate în mod dinamic, care pot furniza adeziv la bibliotecile C / C ++. Acest lucru înseamnă că puteți să luați aproape orice bibliotecă C / C ++ și să creați un addon care vă va permite să îl utilizați în Node.js.

De exemplu, vom crea un pachet pentru standard std :: string obiect.


preparare

Înainte de a începe să scriem, trebuie să vă asigurați că aveți tot ce aveți nevoie pentru a compila modulul mai târziu. Ai nevoie nod-smechera și toate dependențele sale. Puteți instala nod-smechera utilizând următoarea comandă:

npm instalare -g nod-gyp 

În ceea ce privește dependențele, pe sistemele Unix veți avea nevoie de:

  • Python (2.7, 3.x nu va funcționa)
  • face
  • un set de instrumente de compilatoare C ++ (cum ar fi gpp sau g ++)

De exemplu, pe Ubuntu puteți instala toate acestea folosind această comandă (Python 2.7 ar trebui să fie deja instalat):

sudo apt-get instalează build-essentials 

Pe Windows, veți avea nevoie de:

  • Python (2.7.3, 3.x nu va funcționa)
  • Microsoft Visual Studio C ++ 2010 (pentru Windows XP / Vista)
  • Microsoft Visual Studio C ++ 2012 pentru Windows Desktop (Windows 7/8)

Versiunea Express a Visual Studio funcționează bine.


binding.gyp Fişier

Acest fișier este utilizat de către nod-smechera pentru a genera fișiere de construcție adecvate pentru addon. Întregul .escroc documentația fișierelor poate fi găsită pe pagina lor Wiki, dar pentru scopurile noastre acest fișier simplu va face:

"ținte": ["target_name": "stdstring", "surse": ["addon.cc", "stdstring.cc"]] 

TARGET_NAME poate fi orice nume doriți. surse array conține toate fișierele sursă pe care le folosește addonul. În exemplul nostru, există addon.cc, care va conține codul care este necesar pentru a compila adunarea noastră și stdstring.cc, care va conține clasa de ambalaj.


STDStringWrapper Clasă

Vom începe prin definirea clasei noastre în stdstring.h fişier. Primele două linii ar trebui să vă fie familiare dacă ați programat vreodată în C++.

#ifndef STDSTRING_H #define STDSTRING_H 

Acesta este un standard include paza. Apoi, trebuie să includeți aceste două titluri:

#include  #include 

Prima este pentru std :: string clasa și a doua include este pentru toate lucrurile legate de Node și V8.

După aceea, putem declara clasa noastră:

clasa STDStringWrapper: nod public :: ObjectWrap  

Pentru toate clasele pe care dorim să le includem în addonul nostru, trebuie să extindem nod :: ObjectWrap clasă.

Acum putem începe să definim privat proprietățile clasei noastre:

 privat: std :: string * s_; explicit STDStringWrapper (std :: șir s = ""); ~ STDStringWrapper (); 

În afară de constructor și destructor, definim de asemenea un pointer std :: string. Acesta este nucleul tehnicii care poate fi folosit pentru lipirea bibliotecilor C / C ++ la nod - definim un pointer privat la clasa C / C ++ si ulterior operam pe acel pointer in toate metodele.

Apoi declarăm constructor o proprietate statică, care va deține funcția care va crea clasa noastră în V8:

 static v8 :: Maner Nou (const v8 :: Argumente & args); 

Consultați secțiunea v8 :: persistente șablon pentru mai multe informații.

Acum, vom avea și noi Nou , care va fi atribuită constructor de mai sus, atunci când V8 inițiază clasa noastră:

 static v8 :: Manipulați cu noi (const v8 :: Argumente & args); 

Fiecare funcție pentru V8 va arăta astfel: va accepta o referință la Argumentele :: v8 obiect și returnați a v8 :: Manevrați> v8 :: Valoare> - acesta este modul în care V8 se ocupă cu un JavaScript slab tastat atunci când ne programați în tastatură puternic C++.

După aceasta, vom avea două metode care vor fi inserate în prototipul obiectului nostru:

 static v8 :: Maner adăugați (const v8 :: Argumente & args); static v8 :: Maner toString (const v8 :: Argumente & args);

toString () metoda ne va permite să obținem valoarea s_ in loc de [Obiect obiect] când îl folosim cu șiruri de caractere JavaScript normale.

În cele din urmă, vom avea metoda de inițializare (aceasta va fi apelată de către V8 pentru a atribui constructor funcția) și putem închide paza includ:

public: void static Init (v8 :: Handle export); ; # endif

exporturi obiect este echivalent cu module.exports în modulele JavaScript.


stdstring.cc Fișier, Constructor și Distructor

Acum creați stdstring.cc fişier. Mai întâi trebuie să includeți antetul nostru:

#include "stdstring.h" 

Și definiți constructor proprietate (din moment ce este statică):

v8 :: persistente STDStringWrapper :: constructor;

Constructorul pentru clasa noastră va aloca doar s_ proprietate:

STDStringWrapper :: STDStringWrapper (std :: șir s) s_ = nou std :: șir (e);  

Și distrugătorul va face asta șterge pentru a evita o scurgere de memorie:

STDStringWrapper :: ~ STDStringWrapper () șterge s_;  

Si tu trebuie sa șterge tot ce alocați nou, de fiecare dată când există o șansă ca o excepție să fie aruncată, deci țineți cont de aceasta sau folosiți indicatori comuni.


init Metodă

Această metodă va fi apelată de V8 pentru a inițializa clasa noastră (alocați constructor, pune tot ce vrem să folosim în JavaScript în exporturi obiect):

void STDStringWrapper :: Init (v8 :: Mâner exporturile) 

Mai întâi trebuie să creăm un șablon de funcții pentru noi Nou metodă:

v8 :: Local tpl = v8 :: FunctionTemplate :: Nou (nou);

Este cam așa noua funcție în JavaScript - ne permite să pregătim clasa JavaScript.

Acum putem seta numele acestei funcții dacă vrem (dacă omiteți acest lucru, constructorul dvs. va fi anonim, ar fi funcția someName () impotriva funcția () ):

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

Noi am folosit v8 :: String :: NewSymbol () care creează un tip special de șir utilizat pentru nume de proprietăți - acest lucru economisește puțin timp motorul.

Apoi, stabilim câte câmpuri vor avea fiecare instanță din clasa noastră:

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

Avem două metode - adăuga() și toString (), așa că am stabilit asta 2.

Acum putem adăuga metodele noastre la prototipul funcției:

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

Acest lucru pare a fi o mulțime de cod, dar când vă uitați atent, veți vedea un model acolo: vom folosi tpl-> PrototypeTemplate () -> Set () pentru a adăuga fiecare dintre metode. De asemenea, le dăm un nume (folosind v8 :: String :: NewSymbol ()) și a FunctionTemplate.

În cele din urmă, putem pune constructorul în constructor proprietatea clasei noastre și în exporturi obiect:

 constructor = v8 :: Persistent:: New (tpl-> GetFunction ()); export-> Set (v8 :: String :: NewSymbol ("STDString"), constructor); 

Nou Metodă

Acum vom defini metoda care va acționa ca un JavaScript Object.prototype.constructor:

v8 :: Manipulați STDStringWrapper :: Noi (const v8 :: Argumente & args) 

Mai întâi trebuie să creăm un domeniu de aplicare:

 v8 :: Scopul HandleScope; 

După aceea, putem folosi .IsConstructCall () metodă a args obiect pentru a verifica dacă funcția constructorului a fost apelată utilizând nou cuvinte cheie:

 dacă (args.IsConstructCall ())  

Dacă da, să transformăm mai întâi argumentul transmis std :: string asa:

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

... astfel încât să îl putem transmite constructorului clasei noastre de învelitoare:

 STDStringWrapper * obj = nou STDStringWrapper (e); 

După aceea, putem folosi .Wrap () metoda obiectului pe care l-am creat (care este moștenit de la nod :: ObjectWrap) pentru al aloca acest variabil:

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

În cele din urmă, putem returna obiectul nou creat:

 retur args.Aceasta (); 

Dacă funcția nu a fost apelată utilizând nou, vom invoca constructorul așa cum ar fi. Apoi, să creăm o constantă pentru numărul de argumente:

  altceva const int argc = 1; 

Acum, să creăm o matrice cu argumentul nostru:

v8 :: Manipulați STDStringWrapper :: adaugă (const v8 :: Argumente & args) 

Și treci rezultatul constructor-> NewInstance metoda de a scope.Close, astfel încât obiectul poate fi folosit mai târziu (scope.Close vă permite să păstrați mânerul pe un obiect prin mutarea acestuia în sfera superioară - astfel funcționează funcțiile):

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

adăuga Metodă

Acum să creăm adăuga care vă va permite să adăugați ceva la interior std :: string din obiectul nostru:

v8 :: Manipulați STDStringWrapper :: adaugă (const v8 :: Argumente & args) 

Mai întâi trebuie să creăm un domeniu pentru funcția noastră și să convertim argumentul la std :: string cum am făcut mai devreme:

 v8 :: Scopul HandleScope; v8 :: String :: Utf8Value str (args [0] -> ToString ()); std :: șir s (* str); 

Acum trebuie să despachetăm obiectul. Această inversare a ambalajului pe care am făcut-o mai devreme - de data aceasta vom obține indicatorul obiectului nostru de la acest variabil:

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

Atunci putem accesa s_ proprietate și utilizarea lui .adăuga() metodă:

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

În cele din urmă, vom returna valoarea curentă a s_ proprietate (din nou, folosind scope.Close):

 întoarcere scope.Close (v8 :: String :: New (obj-> s -> c_str ()));  

Din moment ce v8 :: String :: Nou () metoda acceptă numai pointer de caractere ca valoare, trebuie să le folosim obj-> s _-> c_str () pentru a obține.


toString Metodă

Ultima metodă necesară ne va permite să transformăm obiectul în JavaScript Şir:

v8 :: Manipulați STDStringWrapper :: toString (const v8 :: Argumente & args) 

Este similar cu cel precedent, trebuie să creăm domeniul de aplicare:

 v8 :: Scopul HandleScope; 

Desfaceți obiectul:

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

Și întoarce-te s_ proprietate ca a v8 :: String:

 întoarcere scope.Close (v8 :: String :: New (obj-> s -> c_str ()));  

clădire

Ultimul lucru pe care trebuie să-l facem înainte de a folosi atașamentul nostru este, bineînțeles, compilarea și legătura. Aceasta va implica doar două comenzi. Primul:

nodul-gyp configure 

Aceasta va crea configurația potrivită pentru OS și procesor (Makefile pe UNIX și vcxproj pe Windows). Pentru a compila și a lega biblioteca, trebuie doar să apelați:

nod-gip construit 

Dacă totul merge bine, ar trebui să vedeți ceva de genul acesta în consola dvs.:

Și ar trebui să fie a construi director creat în dosarul dvs. de addon.

Testarea

Acum putem testa atașamentul nostru. Creeaza o test.js fișier în dosarul dvs. de addon și necesită biblioteca compilată (puteți omite .nodul extensie):

var addon = necesită ('./ build / Release / addon'); 

Apoi, creați o nouă instanță a obiectului nostru:

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

Și faceți ceva cu ea, cum ar fi adăugarea sau conversia într-un șir:

test.add ( '!'); console.log ('conținutul testului:% s', test); 

Acest lucru ar trebui să ducă la ceva asemănător cu cel din consola următoare, după rularea acestuia:

Concluzie

Sper că după ce ați citit acest tutorial nu veți mai crede că este dificil să creați, să construiți și să testați dependente Node.js personalizate bazate pe bibliotecă C / C ++. Folosind această tehnică puteți porni aproape orice bibliotecă C / C ++ la Node.js cu ușurință. Dacă doriți, puteți adăuga mai multe caracteristici la addon-ul pe care l-am creat. Există o mulțime de metode în std :: string pentru a vă exersa.


Link-uri utile

Consultați următoarele resurse pentru mai multe informații despre dezvoltarea de aplicații Node.js, V8 și biblioteca de buletine de evenimente C.

  • Node.js Addons Documentation
  • Documentație V8
  • libuv (Biblioteca de buletine de evenimente C) pe GitHub
Cod