Node.js-addons schrijven

Node.js is geweldig voor het schrijven van uw back-end in JavaScript. Maar wat als u bepaalde functionaliteit nodig heeft die niet uit de doos wordt geleverd of die ook niet met modules kan worden bereikt, maar is beschikbaar in de vorm van een C / C ++ -bibliotheek? Nou, ontzettend goed, je kunt een add-on schrijven waarmee je deze bibliotheek in je JavaScript-code kunt gebruiken. Laten we kijken hoe.

Zoals u in de Node.js-documentatie kunt lezen, zijn add-ons dynamisch gekoppelde gezamenlijke objecten die lijm aan C / C ++ -bibliotheken kunnen leveren. Dit betekent dat u vrijwel elke C / C ++ -bibliotheek kunt gebruiken en een add-on kunt maken waarmee u deze in Node.js kunt gebruiken.

Als voorbeeld zullen we een wrapper voor de standaard maken std :: string voorwerp.


Voorbereiding

Voordat we beginnen met schrijven, moet je ervoor zorgen dat je alles hebt wat je nodig hebt om de module later te compileren. Jij hebt nodig knooppunt-bediende en al zijn afhankelijkheden. U kunt installeren knooppunt-bediende met behulp van de volgende opdracht:

npm install -g node-gyp 

Wat betreft de afhankelijkheden, op Unix-systemen heb je nodig:

  • Python (2.7, 3.x zal niet werken)
  • maken
  • een C ++ compiler toolchain (zoals gpp of g ++)

Op Ubuntu kunt u dit bijvoorbeeld allemaal installeren met deze opdracht (Python 2.7 zou al moeten zijn geïnstalleerd):

sudo apt-get install build-essentials 

Op Windows heb je nodig:

  • Python (2.7.3, 3.x zal niet werken)
  • Microsoft Visual Studio C ++ 2010 (voor Windows XP / Vista)
  • Microsoft Visual Studio C ++ 2012 voor Windows Desktop (Windows 7/8)

De Express-versie van Visual Studio werkt prima.


De binding.gyp het dossier

Dit bestand wordt gebruikt door knooppunt-bediende om geschikte build-bestanden voor uw add-on te genereren. Het geheel .dienaar bestandsdocumentatie is te vinden op hun Wiki-pagina, maar voor onze doeleinden zal dit eenvoudige bestand het volgende doen:

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

De target_name kan elke gewenste naam zijn. De bronnen array bevat alle bronbestanden die de add-on gebruikt. In ons voorbeeld is dat zo addon.cc, die de code bevat die nodig is om onze add-on en te compileren stdstring.cc, die onze wrapper klasse zal bevatten.


De STDStringWrapper Klasse

We zullen beginnen met het definiëren van onze klasse in de stdstring.h het dossier. De eerste twee regels zouden je bekend moeten zijn als je ooit in C hebt geprogrammeerd++.

#ifndef STDSTRING_H #define STDSTRING_H 

Dit is een standaard inclusief bewaker. Vervolgens moeten we deze twee headers opnemen:

#include  #include 

De eerste is voor de std :: string klasse en de tweede include is voor alle dingen gerelateerd aan Node en V8.

Daarna kunnen we onze klas declareren:

class STDStringWrapper: public node :: ObjectWrap  

Voor alle klassen die we in onze add-on willen opnemen, moeten we de knooppunt :: ObjectWrap klasse.

Nu kunnen we beginnen met definiëren privaat eigenschappen van onze klasse:

 privé: std :: string * s_; expliciete STDStringWrapper (std :: string s = ""); ~ STDStringWrapper (); 

Naast de constructor en destructor, definiëren we ook een pointer naar std :: string. Dit is de kern van de techniek die kan worden gebruikt om C / C ++ -bibliotheken aan Node te lijmen - we definiëren een persoonlijke verwijzing naar de C / C ++ -klasse en werken later op die aanwijzer in alle methoden.

Vervolgens verklaren we de bouwer statische eigenschap, die de functie bevat die onze klasse in V8 zal maken:

 static v8 :: verwerken Nieuw (const v8 :: Arguments & args); 

Raadpleeg de v8 :: Persistent sjabloondocumentatie voor meer informatie.

Nu zullen we ook een hebben nieuwe methode, die zal worden toegewezen aan de bouwer hierboven, wanneer V8 onze klasse initialiseert:

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

Elke functie voor V8 ziet er als volgt uit: het accepteert een verwijzing naar de v8 :: Argumenten object en retourneer a v8 :: Stelen> v8 :: Value> - dit is hoe V8 omgaat met zwak getypeerde JavaScript wanneer we programmeren in sterk getypte C++.

Daarna zullen we twee methoden hebben die zullen worden ingevoegd in het prototype van ons object:

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

De toString () methode zal ons in staat stellen om de waarde van te krijgen S_ in plaats van [Object Object] wanneer we het gebruiken met normale JavaScript-strings.

Ten slotte zullen we de initialisatiemethode hebben (deze wordt door V8 genoemd om de bouwer functie) en we kunnen de include-guard sluiten:

public: static void Init (v8 :: Handle uitvoer); ; #stop als

De export object is gelijk aan de module.exports in JavaScript-modules.


De stdstring.cc Bestand, Constructor en Destructor

Maak nu het stdstring.cc het dossier. Eerst moeten we onze header opnemen:

# include "stdstring.h" 

En definieer de bouwer eigenschap (aangezien het statisch is):

v8 :: Persistent STDStringWrapper :: constructeur;

De constructor voor onze klasse zal alleen de S_ eigendom:

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

En de destructor zal verwijderen om een ​​geheugenlek te voorkomen:

STDStringWrapper :: ~ STDStringWrapper () delete s_;  

Ook u moet verwijderen alles wat u toewijst nieuwe, elke keer dat er een kans is dat er een uitzondering wordt gegenereerd, houd daar dan rekening mee of gebruik gedeelde pointers.


De In het Methode

Deze methode wordt door V8 aangeroepen om onze klasse te initialiseren (wijs de klasse toe bouwer, zet alles wat we willen gebruiken in JavaScript in de export voorwerp):

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

Eerst moeten we een functiesjabloon maken voor onze nieuwe methode:

v8 :: Lokale tpl = v8 :: FunctionTemplate :: Nieuw (Nieuw);

Dit is een beetje zoals nieuwe functie in JavaScript - hiermee kunnen we onze JavaScript-klasse voorbereiden.

Nu kunnen we de naam van deze functie instellen als we dat willen (als u dit weglaat, is uw constructeur anoniem, zou het function someName () versus function () ):

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

We gebruikten v8 :: String :: NewSymbol () waarbij een speciaal soort tekenreeks wordt gebruikt voor eigenschapnamen - dit bespaart de motor een beetje tijd.

Daarna stellen we in hoeveel velden elk exemplaar van onze klasse zal hebben:

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

We hebben twee methoden - toevoegen() en toString (), dus we hebben dit ingesteld 2.

Nu kunnen we onze methoden toevoegen aan het prototype van de functie:

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

Dit lijkt veel code, maar als je goed kijkt, zie je daar een patroon: we gebruiken tpl-> PrototypeTemplate () -> Set () om elk van de methoden toe te voegen. We geven ook elk van hen een naam (gebruik v8 :: String :: NewSymbol ()) en een FunctionTemplate.

Ten slotte kunnen we de constructor in de bouwer eigendom van onze klasse en in de export voorwerp:

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

De nieuwe Methode

Nu zullen we de methode definiëren die zal fungeren als een JavaScript Object.prototype.constructor:

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

Eerst moeten we er ruimte voor creëren:

 v8 :: HandleScope scope; 

Daarna kunnen we de gebruiken .IsConstructCall () methode van de args object om te controleren of de constructorfunctie is aangeroepen met de nieuwe trefwoord:

 if (args.IsConstructCall ())  

Als dat zo is, laten we eerst het argument omzetten dat is doorgegeven std :: string zoals dit:

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

... zodat we het kunnen doorgeven aan de constructeur van onze wrapper-klasse:

 STDStringWrapper * obj = new STDStringWrapper (s); 

Daarna kunnen we de gebruiken .Wikkelen() methode van het object dat we hebben gemaakt (waarvan er een is geërfd van knooppunt :: ObjectWrap) om het toe te wijzen aan de deze variabele:

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

Ten slotte kunnen we het nieuw gemaakte object retourneren:

 return args.This (); 

Als de functie niet is aangeroepen met nieuwe, we zullen gewoon de constructor aanroepen zoals hij zou zijn. Laten we vervolgens een constante maken voor het aantal argumenten:

  else const int argc = 1; 

Laten we nu een array maken met ons argument:

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

En geef het resultaat van de constructor-> newInstance methode om scope.Close, zodat het object later kan worden gebruikt (scope.Close laat je in principe het handvat van een object behouden door het naar de hogere scope te verplaatsen - dit is hoe de functies werken):

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

De toevoegen Methode

Laten we nu de toevoegen methode waarmee u iets aan de interne kunt toevoegen std :: string van ons object:

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

Eerst moeten we een bereik creëren voor onze functie en het argument converteren naar std :: string zoals we eerder deden:

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

Nu moeten we het object uitpakken. Dit is het omgekeerde van de verpakking die we eerder hebben gedaan - deze keer krijgen we de aanwijzer naar ons object van de deze variabele:

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

Dan hebben we toegang tot de S_ eigendom en gebruik het .toevoegen () methode:

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

Ten slotte retourneren we de huidige waarde van de S_ eigendom (opnieuw, met behulp van scope.Close):

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

Sinds de v8 :: String :: Nieuwe () methode accepteert alleen char pointer als een waarde moeten we gebruiken obj-> s _-> c_str () om het te krijgen.


De toString Methode

Met de laatste methode kunnen we het object converteren naar JavaScript's Draad:

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

Het lijkt op de vorige, we moeten de scope creëren:

 v8 :: HandleScope scope; 

Pak het object uit:

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

En keer terug S_ eigendom als een v8 :: String:

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

Gebouw

Het laatste wat je moet doen voordat we onze add-on gebruiken, is natuurlijk compilatie en linking. Het zal slechts twee commando's bevatten. Eerste:

node-gyp configureren 

Hiermee wordt de juiste buildconfiguratie voor uw besturingssysteem en processor gemaakt (Makefile op UNIX en vcxproj op Windows). Als u de bibliotheek wilt compileren en koppelen, belt u gewoon:

node-gyp build 

Als alles goed gaat, zou u zoiets in uw console moeten zien:

En er zou een moeten zijn bouwen map gemaakt in de map van uw add-on.

testen

Nu kunnen we onze add-on testen. Maak een test.js bestand in de map van uw addon en vereisen de gecompileerde bibliotheek (u kunt de .knooppunt uitbreiding):

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

Maak vervolgens een nieuw exemplaar van ons object:

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

En doe er iets mee, zoals het toevoegen of converteren naar een string:

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

Dit zou in het spel moeten resulteren in het volgende in de console, na het te hebben uitgevoerd:

Conclusie

Ik hoop dat je na het lezen van deze tutorial niet langer zult denken dat het moeilijk is om op maat gemaakte C / C ++ -bibliotheekgebaseerde, Node.js-add-ons te maken, te bouwen en uit te testen. Met deze techniek kun je vrijwel elke C / C ++ -bibliotheek gemakkelijk naar Node.js verplaatsen. Als u wilt, kunt u meer functies toevoegen aan de add-on die we hebben gemaakt. Er zijn veel methoden in std :: string voor jou om mee te oefenen.


handige links

Bekijk de volgende bronnen voor meer informatie over Node.js addon-ontwikkeling, V8 en de C-event-loopbibliotheek.

  • Node.js Addons-documentatie
  • V8-documentatie
  • libuv (C-event-loopbibliotheek) op GitHub