Skriver Node.js Addons

Node.js er flott for å skrive din back-end i JavaScript. Men hva om du trenger litt funksjonalitet som ikke er gitt ut av boksen, eller som heller ikke kan oppnås selv ved hjelp av moduler, men er tilgjengelig i form av et C / C + + -bibliotek? Vel, awesomely nok, kan du skrive et tillegg som lar deg bruke dette biblioteket i JavaScript-koden din. La oss se hvordan.

Som du kan lese i dokumentasjonen Node.js, er addons dynamisk koblede delte objekter som kan gi lim til C / C ++-biblioteker. Dette betyr at du kan ta omtrent alle C / C ++-bibliotek og opprette et tillegg som lar deg bruke det i Node.js.

Som et eksempel, vil vi lage en wrapper for standarden std :: string gjenstand.


Forberedelse

Før vi begynner å skrive, må du sørge for at du har alt du trenger for å kompilere modulen senere. Du trenger node-gyp og alle dens avhengigheter. Du kan installere node-gyp bruker følgende kommando:

npm installere -g node-gyp 

Når det gjelder avhengighetene, på Unix-systemene, trenger du:

  • Python (2,7, 3.x fungerer ikke)
  • gjøre
  • en C ++-kompilatorverktøyskjede (som gpp eller g ++)

For eksempel på Ubuntu kan du installere alt dette ved hjelp av denne kommandoen (Python 2.7 skal allerede installeres):

sudo apt-get install build-essentials 

På Windows trenger du:

  • Python (2.7.3, 3.x fungerer ikke)
  • Microsoft Visual Studio C ++ 2010 (for Windows XP / Vista)
  • Microsoft Visual Studio C ++ 2012 for Windows Desktop (Windows 7/8)

Express-versjonen av Visual Studio fungerer fint.


De binding.gyp Fil

Denne filen brukes av node-gyp å generere passende byggefiler for tillegget ditt. Hele .gyp fildokumentasjon finnes på deres Wiki-side, men for vår hensikt vil denne enkle filen gjøre:

"mål": ["målnavn": "stdstring", "kilder": ["addon.cc", "stdstring.cc"]] 

De TARGET_NAME kan være noe navn du liker. De kilder array inneholder alle kildefilene som tillegget bruker. I vårt eksempel er det addon.cc, som vil inneholde koden som kreves for å kompilere vår addon og stdstring.cc, som vil inneholde vår wrapper klasse.


De STDStringWrapper Klasse

Vi vil begynne med å definere vår klasse i stdstring.h fil. De to første linjene skal være kjent for deg hvis du noen gang har programmert i C++.

#ifndef STDSTRING_H #define STDSTRING_H 

Dette er en standard som inkluderer vakt. Deretter må vi inkludere disse to overskriftene:

#inkludere  #inkludere 

Den første er for std :: string klassen og den andre inkluderer er for alle ting relatert til Node og V8.

Deretter kan vi erklære vår klasse:

klasse STDStringWrapper: offentlig nod :: ObjectWrap  

For alle klasser som vi vil inkludere i tillegget vårt, må vi utvide node :: ObjectWrap klasse.

Nå kan vi begynne å definere privat egenskaper av vår klasse:

 privat: std :: streng * s_; eksplisitt STDStringWrapper (std :: streng s = ""); ~ STDStringWrapper (); 

Bortsett fra konstruktøren og destruktoren, definerer vi også en peker til std :: string. Dette er kjernen i teknikken som kan brukes til å lime C / C ++-bibliotek til Node - vi definerer en privat peker til C / C ++-klassen og opererer senere på den pekeren i alle metoder.

Neste erklærer vi konstruktør statisk egenskap, som vil holde funksjonen som vil skape vår klasse i V8:

 statisk v8 :: Håndter Nytt (const v8 :: Argumenter og args); 

Vennligst se på v8 :: Vedvarende mal dokumentasjon for mer informasjon.

Nå vil vi også ha en Ny metode, som vil bli tildelt til konstruktør ovenfor, når V8 initialiserer vår klasse:

 statisk v8 :: Håndtere ny (const v8 :: Argumenter og args); 

Hver funksjon for V8 vil se slik ut: den vil akseptere en referanse til v8 :: Argumenter objekt og returnere a v8 :: Håndtak> v8 :: Verdi> - Dette er hvordan V8 omhandler svakt skrevet JavaScript når vi programmerer i sterk-skrevet C++.

Etter det vil vi ha to metoder som vil bli satt inn i prototypen av objektet vårt:

 statisk v8 :: Håndter legg til (const v8 :: Argumenter & args); statisk v8 :: Håndter toString (const v8 :: Argumenter og args);

De toString () Metoden vil tillate oss å få verdien av s_ i stedet for [Objektobjekt] når vi bruker den med vanlige JavaScript strenger.

Til slutt vil vi ha initialiseringsmetoden (dette vil bli kalt av V8 for å tilordne konstruktør funksjon), og vi kan lukke den inkluderte vakt:

offentlig: statisk tomrom Init (v8 :: Håndter utførsel); ; #slutt om

De eksport objektet tilsvarer module.exports i JavaScript-moduler.


De stdstring.cc File, Constructor og Destructor

Opprett nå stdstring.cc fil. Først må vi inkludere vår overskrift:

#include "stdstring.h" 

Og definer konstruktør eiendom (siden det er statisk):

v8 :: Vedvarende STDStringWrapper :: konstruktør;

Konstruktøren for vår klasse vil bare tildele s_ eiendom:

STDStringWrapper :: STDStringWrapper (std :: streng s) s_ = new std :: streng (er);  

Og destruktoren vil slette for å unngå hukommelseskap:

STDStringWrapper :: ~ STDStringWrapper () delete s_;  

Også deg slette alt du tildeler med ny, hver gang det er en sjanse for at et unntak vil bli kastet, så hold det i bakhodet eller bruk felles pekere.


De I det Metode

Denne metoden vil bli kalt av V8 for å initialisere vår klasse (tilordne konstruktør, sett alt vi vil bruke i JavaScript i eksport gjenstand):

void STDStringWrapper :: Init (v8 :: Håndter eksport) 

Først må vi lage en funksjonsmal for vår Ny metode:

v8 :: Local tpl = v8 :: Funksjonstemplat :: Ny (Ny);

Dette er liksom ny funksjon i JavaScript - det tillater oss å forberede JavaScript-klassen vår.

Nå kan vi sette navnet på denne funksjonen hvis vi vil (hvis du utelater dette, vil konstruktøren være anonym, det ville ha funksjon noenName () mot funksjon () ):

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

Vi brukte v8 :: String :: NewSymbol () som skaper en spesiell type streng som brukes til eiendomsnavn - dette sparer motoren litt tid.

Deretter angir vi hvor mange felt hver forekomst av vår klasse vil ha:

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

Vi har to metoder - Legg til() og toString (), så vi satte dette til 2.

Nå kan vi legge til våre metoder til funksjonens prototype:

tpl-> PrototypeTemplate () -> Set (v8 :: String :: NewSymbol ("add"), v8 :: FunctionTemplate :: Nytt (legg til) -> GetFunction ()); tpl-> PrototypeTemplate () -> Set (v8 :: String :: NewSymbol ("toString"), v8 :: FunctionTemplate :: Ny (toString) -> GetFunction ());

Dette virker som en masse kode, men når du ser nøye ut, ser du et mønster der: vi bruker tpl-> PrototypeTemplate () -> Set () å legge til hver av metodene. Vi gir også hvert navn et navn (ved bruk av v8 :: String :: NewSymbol ()) og a FunctionTemplate.

Til slutt kan vi sette konstruktøren i konstruktør eiendom av vår klasse og i eksport gjenstand:

 constructor = v8 :: Vedvarende:: Nye (tpl-> GetFunction ()); eksport-> Set (v8 :: String :: NewSymbol ("STDString"), konstruktør); 

De Ny Metode

Nå skal vi definere metoden som vil fungere som et JavaScript Object.prototype.constructor:

v8 :: Håndtak STDStringWrapper :: Nytt (const v8 :: Argumenter og args) 

Først må vi skape et omfang for det:

 v8 :: HandleScope omfang; 

Deretter kan vi bruke .IsConstructCall () metode av args motsette seg om konstruktørfunksjonen ble kalt ved hjelp av ny søkeord:

 hvis (args.IsConstructCall ())  

Hvis ja, la oss først konvertere argumentet til std :: string som dette:

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

... slik at vi kan sende den til konstruktøren til vår innpakningsklasse:

 STDStringWrapper * obj = ny STDStringWrapper (e); 

Deretter kan vi bruke .Pakke inn() Metode for objektet vi opprettet (som er arvet fra node :: ObjectWrap) for å tilordne den til dette variabel:

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

Endelig kan vi returnere det nyopprettede objektet:

 returner args.This (); 

Hvis funksjonen ikke ble kalt ved bruk ny, Vi vil bare påkalle konstruktøren som det ville være. Deretter la vi lage en konstant for argumenttellingen:

  ellers const int argc = 1; 

La oss nå lage en matrise med vårt argument:

v8 :: Håndtak STDStringWrapper :: legg til (const v8 :: Argumenter og args) 

Og passere resultatet av constructor-> NewInstance metode til scope.Close, så objektet kan brukes senere (scope.Close i utgangspunktet lar deg bevare håndtaket til et objekt ved å flytte det til det høyere omfanget - slik fungerer funksjonene):

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

De Legg til Metode

La oss nå lage Legg til metode som vil tillate deg å legge til noe på det interne std :: string av vårt objekt:

v8 :: Håndtak STDStringWrapper :: legg til (const v8 :: Argumenter og args) 

Først må vi skape et omfang for vår funksjon og konvertere argumentet til std :: string som vi gjorde tidligere:

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

Nå må vi pakke ut objektet. Dette omvendt av innpakningen vi gjorde tidligere - denne gangen vil vi få pekeren til vårt objekt fra dette variabel:

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

Da får vi tilgang til s_ eiendom og bruk dens .føye () metode:

 obj-> s _-> tilføy (s); 

Endelig returnerer vi nåverdien av s_ eiendom (igjen, bruk scope.Close):

 returnere scope.Close (v8 :: String :: Nytt (obj-> s _-> c_str ()));  

Siden v8 :: String :: Nye () Metoden aksepterer bare char pointer Som en verdi må vi bruke obj-> s _-> c_str () å få det.


De toString Metode

Den siste metoden som trengs vil tillate oss å konvertere objektet til JavaScript string:

v8 :: Håndtak STDStringWrapper :: toString (const v8 :: Argumenter og args) 

Det ligner på den forrige, vi må skape omfanget:

 v8 :: HandleScope omfang; 

Unwrap objektet:

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

Og returnere s_ eiendom som en v8 :: String:

 returnere scope.Close (v8 :: String :: Nytt (obj-> s _-> c_str ()));  

Bygning

Den siste tingen å gjøre før vi bruker vår addon, er selvfølgelig kompilering og linking. Det vil bare innebære to kommandoer. Først:

node-gyp configure 

Dette vil skape riktig byggekonfigurasjon for operativsystemet og prosessoren (Makefile på UNIX og vcxproj på Windows). For å kompilere og lenke biblioteket, ring bare:

node-gyp build 

Hvis alt går bra, bør du se noe slikt i konsollen din:

Og det skal være en bygge katalog opprettet i addons mappe.

testing

Nå kan vi teste vår addon. Lage en test.js fil i tilleggsmappen din og krever det kompilerte biblioteket (du kan utelate .node forlengelse):

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

Deretter oppretter du en ny forekomst av objektet vårt:

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

Og gjør noe med det, som å legge til eller konvertere det til en streng:

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

Dette bør resultere i noe som følger i konsollen, etter at du har kjørt det:

Konklusjon

Jeg håper at etter å ha lest denne opplæringen, vil du ikke lenger tro at det er vanskelig å lage, bygge og teste ut tilpassede C / C ++-biblioteksbaserte, Node.js addons. Ved hjelp av denne teknikken kan du enkelt portere noe C / C ++-bibliotek til Node.js. Hvis du vil, kan du legge til flere funksjoner i tillegget vi opprettet. Det er mange metoder i std :: string for å trene med.


nyttige lenker

Ta en titt på følgende ressurser for mer informasjon om Node.js addon development, V8 og C event loop biblioteket.

  • Node.js Addons Dokumentasjon
  • V8-dokumentasjon
  • libuv (C hendelsesløkkebibliotek) på GitHub