Skriva Node.js Addons

Node.js är utmärkt för att skriva ditt back-end i JavaScript. Men vad händer om du behöver någon funktionalitet som inte är utelämnad eller som inte kan åstadkommas även med moduler, men är Finns i form av ett C / C ++-bibliotek? Nåväl nog, du kan skriva ett tillägg som låter dig använda det här biblioteket i din JavaScript-kod. Låt oss se hur.

Som du kan läsa i dokumentationen Node.js, är addons dynamiskt länkade delade objekt som kan ge lim till C / C ++-bibliotek. Det betyder att du kan ta ganska mycket C / C ++-bibliotek och skapa ett tillägg som låter dig använda det i Node.js.

Som ett exempel kommer vi att skapa ett omslag för standarden std :: string objekt.


Förberedelse

Innan vi börjar skriva måste du se till att du har allt du behöver för att kompilera modulen senare. Du behöver nod-gyp och alla dess beroende. Du kan installera nod-gyp använder följande kommando:

npm installera -g nod-gyp 

När det gäller beroenden, på Unix-system behöver du:

  • Python (2,7, 3.x fungerar inte)
  • göra
  • en C ++-kompilatorverktygskedja (som gpp eller g ++)

Till exempel på Ubuntu kan du installera allt detta med det här kommandot (Python 2.7 bör redan installeras):

sudo apt-get installera bygg-essentials 

På Windows behöver du:

  • Python (2.7.3, 3.x fungerar inte)
  • Microsoft Visual Studio C ++ 2010 (för Windows XP / Vista)
  • Microsoft Visual Studio C ++ 2012 för Windows Desktop (Windows 7/8)

Express-versionen av Visual Studio fungerar bra.


De binding.gyp Fil

Den här filen används av nod-gyp att generera lämpliga byggfiler för din addon. Hela .gyp fildokumentation finns på deras Wiki-sida, men för vårt ändamål kommer den här enkla filen att göra:

"mål": ["målnamn": "stdstring", "källor": ["addon.cc", "stdstring.cc"]] 

De target_name kan vara vilket namn du vill ha. De källor array innehåller alla källfiler som tillägget använder. I vårt exempel finns det addon.cc, som innehåller koden som krävs för att kompilera vår addon och stdstring.cc, som kommer att innehålla vår omslagsklass.


De STDStringWrapper Klass

Vi börjar med att definiera vår klass i stdstring.h fil. De två första linjerna bör vara bekanta för dig om du någonsin har programmerat i C++.

#ifndef STDSTRING_H #define STDSTRING_H 

Detta är en standard inkluderar vakt. Därefter måste vi inkludera dessa två rubriker:

#inkludera  #inkludera 

Den första är för std :: string klass och den andra ingår är för alla saker relaterade till Node och V8.

Därefter kan vi förklara vår klass:

klass STDStringWrapper: allmän nod :: ObjectWrap  

För alla klasser som vi vill inkludera i vårt tillägg måste vi förlänga nod :: ObjectWrap klass.

Nu kan vi börja definiera privat egenskaper i vår klass:

 privat: std :: sträng * s_; explicit STDStringWrapper (std :: sträng s = ""); ~ STDStringWrapper (); 

Bortsett från konstruktören och destruktorn definierar vi också en pekare till std :: string. Detta är kärnan i tekniken som kan användas för att limma C / C ++-bibliotek till Node - vi definierar en privat pekare till C / C ++-klassen och arbetar senare på den pekaren i alla metoder.

Därefter förklarar vi konstruktör statisk egenskap, som kommer att hålla den funktion som kommer att skapa vår klass i V8:

 statisk v8 :: Hantera Nytt (const v8 :: Arguments & args); 

Vänligen hänvisa till v8 :: Ihållande mall dokumentation för mer information.

Nu har vi också en Ny metod som kommer att tilldelas till konstruktör ovan, när V8 initierar vår klass:

 statisk v8 :: Hantera nytt (const v8 :: Arguments & args); 

Varje funktion för V8 kommer att se ut så här: den kommer att acceptera en hänvisning till v8 :: Argument objekt och returnera a v8 :: Handtag> v8 :: Värde> - Det här är hur V8 handlar om svagt typad JavaScript när vi programmerar i starkt typad C++.

Därefter kommer vi att ha två metoder som kommer att införas i prototypen av vårt objekt:

 statisk v8 :: Hantera lägg till (const v8 :: Arguments & args); statisk v8 :: Hantera toString (const v8 :: Arguments & args);

De att stränga() Metoden gör det möjligt för oss att få värdet av s_ istället för [Objektobjekt] när vi använder det med normala JavaScript-strängar.

Slutligen kommer vi att ha initialiseringsmetoden (detta kommer att kallas av V8 för att tilldela konstruktör funktion) och vi kan stänga inkludera vakt:

offentligt: ​​statiskt tomrum Init (v8 :: Hantera export); ; #endif

De export objekt motsvarar module.exports i JavaScript-moduler.


De stdstring.cc File, Constructor och Destructor

Skapa nu stdstring.cc fil. Först måste vi inkludera vår rubrik:

#include "stdstring.h" 

Och definiera konstruktör egendom (eftersom det är statiskt):

v8 :: Ihållande STDStringWrapper :: constructor;

Konstruktören för vår klass kommer bara att fördela s_ fast egendom:

STDStringWrapper :: STDStringWrapper (std :: sträng s) s_ = new std :: sträng (s);  

Och destructorn kommer radera för att undvika minnesläckage:

STDStringWrapper :: ~ STDStringWrapper () delete s_;  

Du också måste radera allt du allokerar med ny, varje gång det finns en chans att ett undantag kommer att kastas, så håll det i åtanke eller använd delade tips.


De I det Metod

Denna metod kommer att kallas av V8 för att initiera vår klass (tilldela konstruktör, sätta allt vi vill använda i JavaScript i export objekt):

void STDStringWrapper :: Init (v8 :: Hantera export) 

Först måste vi skapa en funktionsmall för vår Ny metod:

v8 :: Lokal tpl = v8 :: FunctionTemplate :: Ny (Ny);

Det här är snällt ny funktion i JavaScript - det gör det möjligt för oss att förbereda vår JavaScript-klass.

Nu kan vi ställa in den här funktionens namn om vi vill (om du släpper bort det här kommer din konstruktör att vara anonym, det skulle ha funktionen someName () mot funktion () ):

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

Vi använde v8 :: String :: NewSymbol () vilket skapar en speciell sträng som används för fastighetsnamn - det sparar motorn lite tid.

Därefter bestämmer vi hur många fält varje förekomst av vår klass ska ha:

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

Vi har två metoder - Lägg till() och att stränga(), så vi bestämmer detta för 2.

Nu kan vi lägga till våra metoder till funktionens prototyp:

tpl-> PrototypeTemplate () -> Set (v8 :: String :: NewSymbol ("add"), v8 :: FunktionTemplat :: Ny (lägg till) -> GetFunction ()); tpl-> PrototypeTemplate () -> Set (v8 :: String :: NewSymbol ("toString"), v8 :: FunctionTemplate :: Ny (toString) -> GetFunction ());

Det verkar som mycket kod, men när du tittar noga ser du ett mönster där: vi använder tpl-> PrototypeTemplate () -> Set () att lägga till var och en av metoderna. Vi ger också var och en ett namn (med v8 :: String :: NewSymbol ()) och a FunctionTemplate.

Slutligen kan vi lägga konstruktören i konstruktör egendom i vår klass och i export objekt:

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

De Ny Metod

Nu ska vi definiera den metod som kommer att fungera som en JavaScript Object.prototype.constructor:

v8 :: Handtag STDStringWrapper :: Ny (const v8 :: Arguments & args) 

Först måste vi skapa ett utrymme för det:

 v8 :: HandleScope omfattning; 

Därefter kan vi använda .IsConstructCall () metod för args objekta att kontrollera om konstruktörfunktionen kallades med hjälp av ny nyckelord:

 om (args.IsConstructCall ())  

Om så är fallet, låt oss först konvertera argumentet som skickats till std :: string så här:

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

... så att vi kan vidarebefordra den till konstruktören av vår omslagsklass:

 STDStringWrapper * obj = nya STDStringWrapper (s); 

Därefter kan vi använda .Slå in() Metod för det objekt vi skapade (som är ärvt från nod :: ObjectWrap) för att tilldela den till detta variabel:

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

Slutligen kan vi returnera det nyskapade objektet:

 returnera args.This (); 

Om funktionen inte kallades användande ny, Vi kommer bara att påtala konstruktören som det skulle vara. Låt oss sedan skapa en konstant för argumenträkningen:

  annars const int argc = 1; 

Låt oss nu skapa en array med vårt argument:

v8 :: Handtag STDStringWrapper :: lägg till (const v8 :: Arguments & args) 

Och skicka resultatet av constructor-> NewInstance metod till scope.Close, så objektet kan användas senare (scope.Close låter dig i princip behålla handtaget till ett föremål genom att flytta det till det högre omfånget - så fungerar funktionerna):

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

De Lägg till Metod

Nu ska vi skapa Lägg till metod som låter dig lägga till något på det inre std :: string av vårt objekt:

v8 :: Handtag STDStringWrapper :: lägg till (const v8 :: Arguments & args) 

Först måste vi skapa en räckvidd för vår funktion och konvertera argumentet till std :: string som vi gjorde tidigare:

 v8 :: HandleScope omfattning; v8 :: String :: Utf8Value str (args [0] -> ToString ()); std :: sträng s (* str); 

Nu måste vi ta bort objektet. Detta omvända av omslaget vi gjorde tidigare - den här gången kommer vi att få pekaren till vårt objekt från detta variabel:

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

Då kan vi komma åt s_ egendom och använd dess .bifoga() metod:

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

Slutligen returnerar vi nuvarande värdet av s_ egendom (igen, med hjälp av scope.Close):

 returnera scope.Close (v8 :: String :: Ny (obj-> s _-> c_str ()));  

Sedan v8 :: String :: New () Metoden accepterar endast char pekare som ett värde måste vi använda obj-> s _-> c_str () att få det.


De att stränga Metod

Den sista nödvändiga metoden gör att vi kan konvertera objektet till JavaScript Sträng:

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

Det liknar den tidigare, vi måste skapa omfattningen:

 v8 :: HandleScope omfattning; 

Unwrap objektet:

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

Och återvända s_ egendom som en v8 :: String:

 returnera scope.Close (v8 :: String :: Ny (obj-> s _-> c_str ()));  

Byggnad

Den sista sak att göra innan vi använder vår addon, är naturligtvis kompilering och länkning. Det kommer bara att innehålla två kommandon. Först:

nod-gypkonfigurera 

Detta skapar lämplig byggkonfiguration för ditt operativsystem och processor (Makefile på UNIX och vcxproj på Windows). För att kompilera och länka biblioteket, ring bara:

node-gyp build 

Om allt går bra bör du se något så här i konsolen:

Och det borde finnas en bygga katalog skapad i din addons mapp.

Testning

Nu kan vi testa vårt tillägg. Skapa en test.js filen i din tilläggs mapp och kräva det sammanställda biblioteket (du kan släppa bort den .nod förlängning):

var addon = kräver ('./ build / Release / addon'); 

Skapa sedan en ny instans av vårt objekt:

var test = nytt addon.STDString ("test"); 

Och gör något med det, som att lägga till eller konvertera det till en sträng:

test.add (! '); console.log ('test \' s innehåll:% s ', test); 

Detta bör resultera i något som följer i konsolen, efter att ha kört det:

Slutsats

Jag hoppas att efter att ha läst den här handledningen kommer du inte längre att tro att det är svårt att skapa, bygga och testa anpassade C / C ++-baserade bibliotek, Node.js addons. Med hjälp av denna teknik kan du enkelt porta något C / C ++-bibliotek till Node.js. Om du vill kan du lägga till fler funktioner i tillägget vi skapade. Det finns gott om metoder i std :: string för att du ska träna med.


Användbara länkar

Kolla in följande resurser för mer information om Node.js addon-utveckling, V8 och C-händelsessystemet.

  • Node.js Addons Dokumentation
  • V8-dokumentation
  • libuv (C-händelsessamlingsbibliotek) på GitHub