Node.js Addons schreiben

Node.js eignet sich hervorragend zum Schreiben Ihres Backends in JavaScript. Was aber, wenn Sie eine Funktionalität benötigen, die nicht standardmäßig bereitgestellt wird oder die nicht einmal mit Modulen ausgeführt werden kann, sondern? ist in Form einer C / C ++ - Bibliothek verfügbar? Natürlich können Sie ein Addon schreiben, mit dem Sie diese Bibliothek in Ihrem JavaScript-Code verwenden können. Mal sehen wie.

Wie Sie in der Dokumentation zu Node.js nachlesen können, handelt es sich bei Addons um dynamisch verknüpfte gemeinsam genutzte Objekte, über die C / C ++ - Bibliotheken miteinander verbunden werden können. Das bedeutet, dass Sie praktisch jede beliebige C / C ++ - Bibliothek verwenden und ein Addon erstellen können, mit dem Sie es in Node.js verwenden können.

Als Beispiel erstellen wir einen Wrapper für den Standard std :: string Objekt.


Vorbereitung

Bevor wir mit dem Schreiben beginnen, müssen Sie sicherstellen, dass Sie über alles verfügen, was Sie zum späteren Kompilieren des Moduls benötigen. Du brauchst Knoten-Gyp und alle seine Abhängigkeiten. Sie können installieren Knoten-Gyp mit folgendem Befehl:

npm install -g node-gyp 

Für die Abhängigkeiten auf Unix-Systemen benötigen Sie:

  • Python (2.7, 3.x funktioniert nicht)
  • machen
  • eine C ++ - Compiler-Toolchain (wie gpp oder g ++)

Unter Ubuntu können Sie beispielsweise all dies mit diesem Befehl installieren (Python 2.7 sollte bereits installiert sein):

sudo apt-get install build-essentials 

Unter Windows benötigen Sie:

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

Die Express-Version von Visual Studio funktioniert einwandfrei.


Das Bindung.gyp Datei

Diese Datei wird von verwendet Knoten-Gyp um geeignete Build-Dateien für Ihr Addon zu generieren. Das Ganze .Gyp Die Datei-Dokumentation finden Sie auf ihrer Wiki-Seite. Für unsere Zwecke ist diese einfache Datei jedoch geeignet:

"Ziele": ["Zielname": "Stdstring", "sources": ["addon.cc", "stdstring.cc"]] 

Das Zielname kann ein beliebiger Name sein. Das Quellen Array enthält alle Quelldateien, die das Addon verwendet. In unserem Beispiel gibt es addon.cc, die den Code enthält, der zum Kompilieren unseres Addons und erforderlich ist stdstring.cc, die unsere Wrapper-Klasse enthalten wird.


Das STDStringWrapper Klasse

Wir beginnen mit der Definition unserer Klasse im stdstring.h Datei. Die ersten beiden Zeilen sollten Ihnen bekannt sein, wenn Sie jemals in C programmiert haben++.

#ifndef STDSTRING_H #define STDSTRING_H 

Dies ist ein Standard-Include-Guard. Als nächstes müssen wir diese beiden Header einfügen:

#umfassen  #umfassen 

Der erste ist für den std :: string Klasse und das zweite Include ist für alle Dinge, die sich auf Node und V8 beziehen.

Danach können wir unsere Klasse erklären:

Klasse STDStringWrapper: öffentlicher Knoten :: ObjectWrap  

Für alle Klassen, die wir in unser Addon aufnehmen wollen, müssen wir das erweitern node :: ObjectWrap Klasse.

Jetzt können wir mit der Definition beginnen Privatgelände Eigenschaften unserer Klasse:

 privat: std :: string * s_; expliziter STDStringWrapper (std :: string s = ""); ~ STDStringWrapper (); 

Neben dem Konstruktor und Destruktor definieren wir auch einen Zeiger auf std :: string. Dies ist der Kern der Technik, mit der C / C ++ - Bibliotheken an den Knoten geklebt werden können. Wir definieren einen privaten Zeiger auf die C / C ++ - Klasse und bearbeiten diesen Zeiger später in allen Methoden.

Als nächstes erklären wir die Konstrukteur statische Eigenschaft, die die Funktion enthält, die unsere Klasse in V8 erstellt:

 static v8 :: Handle Neu (const v8 :: Argumente & Argumente); 

Bitte wende dich an die v8 :: beständig Vorlagendokumentation für weitere Informationen.

Jetzt haben wir auch eine Neu Methode, die der zugeordnet wird Konstrukteur oben, wenn V8 unsere Klasse initialisiert:

 static v8 :: Handle New (const v8 :: Argumente & Argumente); 

Jede Funktion für V8 wird folgendermaßen aussehen: Sie akzeptiert einen Verweis auf die v8 :: Argumente Objekt und Rückgabe a v8 :: Handle> v8 :: Value> - Dies ist, wie V8 mit schwach typisiertem JavaScript umgeht, wenn wir in stark typisiertem C programmieren++.

Danach haben wir zwei Methoden, die in den Prototyp unseres Objekts eingefügt werden:

 static v8 :: Handle add (const v8 :: Argumente & Argumente); static v8 :: Handle toString (const v8 :: Argumente & Argumente);

Das toString () Methode wird es uns ermöglichen, den Wert von s_ anstatt [Objekt Objekt] wenn wir es mit normalen JavaScript-Strings verwenden.

Schließlich haben wir die Initialisierungsmethode (diese wird von V8 aufgerufen, um das zuzuweisen Konstrukteur Funktion) und wir können den Include-Guard schließen:

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

Das Exporte Objekt entspricht dem modul.exporte in JavaScript-Modulen.


Das stdstring.cc Datei, Konstruktor und Destruktor

Nun erstellen Sie die stdstring.cc Datei. Zuerst müssen wir unseren Header einfügen:

#include "stdstring.h" 

Und definieren Sie die Konstrukteur Eigenschaft (da es statisch ist):

v8 :: beständig STDStringWrapper :: Konstruktor;

Der Konstruktor für unsere Klasse wird nur das zuweisen s_ Eigentum:

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

Und der Zerstörer wird löschen Um ein Speicherverlust zu vermeiden:

STDStringWrapper :: ~ STDStringWrapper () delete s_;  

Auch du Muss löschen alles, was Sie zugeteilt haben Neu, Jedes Mal, wenn die Möglichkeit besteht, dass eine Ausnahme ausgelöst wird, sollten Sie dies im Auge behalten oder gemeinsame Zeiger verwenden.


Das Drin Methode

Diese Methode wird von V8 aufgerufen, um unsere Klasse zu initialisieren Konstrukteur, setzen Sie alles, was wir in JavaScript verwenden möchten, in das Exporte Objekt):

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

Zuerst müssen wir eine Funktionsvorlage für unsere erstellen Neu Methode:

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

Das ist irgendwie ähnlich neue Funktion in JavaScript - ermöglicht es uns, unsere JavaScript-Klasse vorzubereiten.

Jetzt können wir den Namen dieser Funktion festlegen, wenn Sie möchten (wenn Sie dies weglassen, wird Ihr Konstruktor anonym sein, dies wäre der Fall) Funktion someName () gegen Funktion () ):

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

Wir verwendeten v8 :: String :: NewSymbol () Dadurch entsteht eine spezielle Art von Zeichenfolge, die für Eigenschaftsnamen verwendet wird. Dies spart dem Modul etwas Zeit.

Danach legen wir fest, wie viele Felder jede Instanz unserer Klasse haben wird:

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

Wir haben zwei Methoden - hinzufügen() und toString (), also setzen wir dies auf 2.

Jetzt können wir unsere Methoden zum Prototyp der Funktion hinzufügen:

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

Das scheint viel Code zu sein, aber wenn Sie genau hinschauen, sehen Sie dort ein Muster: Wir verwenden es tpl-> PrototypeTemplate () -> Set () jede der Methoden hinzufügen. Wir geben jedem von ihnen auch einen Namen (mit v8 :: String :: NewSymbol ()) und ein FunctionTemplate.

Schließlich können wir den Konstruktor in die Konstrukteur Eigentum unserer Klasse und in der Exporte Objekt:

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

Das Neu Methode

Nun definieren wir die Methode, die als JavaScript fungiert Object.prototype.constructor:

v8 :: Griff STDStringWrapper :: New (const v8 :: Argumente und Argumente) 

Zuerst müssen wir einen Spielraum dafür schaffen:

 v8 :: HandleScope-Bereich; 

Danach können wir die verwenden .IsConstructCall () Methode der args Objekt, um zu überprüfen, ob die Konstruktorfunktion mit der Funktion aufgerufen wurde Neu Stichwort:

 if (args.IsConstructCall ())  

Wenn ja, konvertieren wir zuerst das Argument, an das übergeben wird std :: string so was:

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

… Damit wir es an den Konstruktor unserer Wrapper-Klasse übergeben können:

 STDStringWrapper * obj = neue STDStringWrapper (s); 

Danach können wir die verwenden .Wickeln() Methode des von uns erstellten Objekts (das von übernommen wurde) node :: ObjectWrap) um es dem zuzuweisen diese Variable:

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

Zum Schluss können wir das neu erstellte Objekt zurückgeben:

 return args.This (); 

Wenn die Funktion nicht mit aufgerufen wurde Neu, Wir rufen den Konstruktor einfach so auf, wie er wäre. Als Nächstes erstellen wir eine Konstante für die Argumentanzahl:

  else const int argc = 1; 

Nun erstellen wir ein Array mit unserem Argument:

v8 :: Griff STDStringWrapper :: add (const v8 :: Argumente und Argumente) 

Und übergeben Sie das Ergebnis der Konstruktor-> NewInstance Methode zu Umfang.Schließen, So kann das Objekt später verwendet werden (Umfang.Schließen Im Grunde können Sie das Handle für ein Objekt beibehalten, indem Sie es in den höheren Bereich verschieben (so funktionieren Funktionen):

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

Das hinzufügen Methode

Lass uns jetzt die erstellen hinzufügen Methode, mit der Sie etwas zum internen hinzufügen können std :: string von unserem Objekt:

v8 :: Griff STDStringWrapper :: add (const v8 :: Argumente und Argumente) 

Zuerst müssen wir einen Bereich für unsere Funktion erstellen und das Argument in konvertieren std :: string wie früher:

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

Nun müssen wir das Objekt auspacken. Dies ist die Umkehrung der Umhüllung, die wir zuvor gemacht haben - dieses Mal erhalten wir den Zeiger auf unser Objekt von diese Variable:

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

Dann können wir auf die zugreifen s_ Eigentum und Verwendung seiner .anhängen () Methode:

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

Zum Schluss geben wir den aktuellen Wert von zurück s_ Eigenschaft (wieder mit Umfang.Schließen):

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

Seit der v8 :: String :: New () Methode akzeptiert nur Zeichenzeiger Als Wert müssen wir verwenden obj-> s _-> c_str () es bekommen.


Das toString Methode

Die letzte benötigte Methode ermöglicht es uns, das Objekt in JavaScript zu konvertieren String:

v8 :: Griff STDStringWrapper :: toString (const v8 :: Argumente und Argumente) 

Es ist dem vorherigen ähnlich, wir müssen den Umfang erstellen:

 v8 :: HandleScope-Bereich; 

Packen Sie das Objekt aus:

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

Und das zurückgeben s_ Eigenschaft als v8 :: String:

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

Gebäude

Das letzte, was wir tun müssen, bevor wir unser Addon verwenden, ist natürlich das Kompilieren und Verknüpfen. Es werden nur zwei Befehle benötigt. Zuerst:

node-gyp configure 

Dadurch wird die entsprechende Build-Konfiguration für Ihr Betriebssystem und Ihren Prozessor erstellt (Makefile unter UNIX und vcxproj unter Windows). Um die Bibliothek zu kompilieren und zu verlinken, rufen Sie einfach an:

Knoten-Gyp-Build 

Wenn alles gut geht, sollten Sie so etwas in Ihrer Konsole sehen:

Und es sollte eine geben bauen Verzeichnis im Ordner Ihres Addons erstellt.

Testen

Jetzt können wir unser Addon testen. Ein ... kreieren test.js Datei im Ordner Ihres Addons und benötigen die kompilierte Bibliothek (Sie können die .Knoten Erweiterung):

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

Als Nächstes erstellen Sie eine neue Instanz unseres Objekts:

var test = neues Addon.STDString ('test'); 

Und machen Sie etwas damit, zB Hinzufügen oder Konvertieren in einen String:

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

Nach der Ausführung sollte dies in der Konsole etwa Folgendes ergeben:

Fazit

Ich hoffe, dass Sie nach der Lektüre dieses Tutorials nicht mehr denken werden, dass es schwierig ist, angepasste, auf C / C ++ - Bibliotheken basierende Node.js-Addons zu erstellen, zu erstellen und zu testen. Mit dieser Technik können Sie fast jede C / C ++ - Bibliothek problemlos in Node.js portieren. Wenn Sie möchten, können Sie dem von uns erstellten Addon weitere Funktionen hinzufügen. Es gibt viele Methoden in std :: string damit du mit üben kannst.


Nützliche Links

Weitere Informationen zur Entwicklung von Node.js-Addons, V8 und der C-Ereignisschleifenbibliothek finden Sie in den folgenden Ressourcen.

  • Node.js Addons Dokumentation
  • V8-Dokumentation
  • libuv (C-Ereignisschleifenbibliothek) auf GitHub