Node.jsC++

Node.js C++ Addon

Ein simples Beispiel für das Erstellen eines nativen Node.js Addons mittels C++.

Es hat sich viel getan im Bereich der Node.js API. Gerade wenn man auf die N-API setzt. Diese ist unabhängig von der zugrundeliegenden JavaScript Laufzeitumgebung und wird als Teil von Node.js gepflegt. Somit sollte es also nicht mehr zu Breaking Changes kommen, wenn man auf eine neue Node.js Version updated, welche mit einer anderen Version von V8 läuft. In Kombination mit dem node-addon-api Modul, welches C++ Wrapper Klassen für die N-API zur Verfügung stellt, wird es relativ angenehm eigene native Addons für Node.js zu entwickeln.

Grundlegendes

Als erstes erstellen wir einen Ordner mit den Namen math-addon, wir wechseln in den neu erstellten Ordner und initialisieren mittels npm init -y das Modul.

Danach installieren wir node-gyp, das node-addon-api Modul und bindings.

$ npm install node-gyp --save-dev
$ npm install node-addon-api bindings --save

Mit node-gyp wird uns der Build Prozess erheblich erleichtert. Es ist ein Command-Line-Tool, welches das erstellen der nativen Node.js Module plattformunabhängig ermöglicht.

Bindings hilft uns beim späteren Laden unserer kompilierten Node Addons.

Das node-addon-api Modul stellt C++ Wrapper Klassen für die in C gehaltene N-API zur Verfügung. Damit sind wir für den Anfang gut aufgestellt.

Konfigurieren von node-gyp

Die Datei binding.gyp ist eine im JSON ähnlichen Format gehaltene Konfigurationsdatei mit der sich node-gyp für das Erstellen des Moduls einstellen lässt.

math-addon/binding.gyp
{
"targets": [
    {
      "target_name": "MathAddon",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [ "src/math.cc" ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
	  'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],
      'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]
    }
  ]
}

Natives Modul

In dem Ordner /math-addon/src, welchen wir vorab erstellen, platzieren wir unsere C++ Source Datei namens math.cc.

math-addon/src/math.cc
#include "napi.h"

using namespace Napi;

Value Square(const CallbackInfo& info) {
  Env env = info.Env();

  if(info.Length() < 1) {
    TypeError::New(env, "Wrong number of arguments")
    	.ThrowAsJavaScriptException();
        
    return env.Null();
  }

  if(!info[0].IsNumber()) {
    TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
    
    return env.Null();
  }

  double arg0 = info[0].As<Number>().DoubleValue();
  Number num = Number::New(env, arg0 * arg0);

  return num;
}

Object Init(Env env, Object exports) {
	exports["square"] = Function::New(env, Square, std::string("Square"));
	return exports;
}

NODE_API_MODULE(addon, Init)

Wir benötigen 2 Funktionen. Zum einem haben wir die Funktion Square. Diese überprüft erst ob das übergebene Argument eine Zahl ist und quadriet es dann. Die Init Funktion wird zum initialisieren benötigt, welches letztendlich vom Makro NODE_API_MODULE übernommen wird.

Das Argument Napi::CallbackInfo info der Square Funktion wird von der Node.js Laufzeitumgebung erstellt und übergeben. Es enthält die vom Caller übergebenen Argumente. Die Anzahl der Argumente wird von der Length Methode zurückgegeben. Auf jedes einzelne Argument kann mit der Methode operator[] zugegriffen werden. Die Env Methode von Napi::CallbackInfo liefert ein Objekt zurück, welches einen Kontext zu v8 und den VM spezifischen Zustand hat.

Die Klasse Napi::Number repräsentiert das Number JavaScript Objekt. Mit der Methode New(napi_env env, double value) wird eine neue Instanz von Napi::Number erstellt. Die DoubleValue Methode konvertiert Napi::Number in den Datentyp double.

Kompilieren des Moduls

Bevor wir kompilieren können müßen wir die Projekt-Build-Dateien generieren. Dazu müßen Sie node-gyp configure in die Konsole eingeben. Danach kann man mit node-gyp build das Modul kompelieren.

Das native Modul nutzen

Wir erstellen in dem Ordner /math-addon eine JavaScript Datei mit folgendem Inhalt.

math-addon/math.js
const MathAddon = require('bindings')('MathAddon');

const number = 8;

console.log(`${number}² = ${MathAddon.square(number)}`);

Nun führen wir math.js mit Node.js aus.

~/math-addon$ node math.js
8² = 64