Einführung in native NodeJS-Module

Hinweis: Dieser Artikel enthält keine Informationen zum WASM-Standard. Hier ist
haben nur die altmodische C++-API zum Erstellen von Node.js-Modulen besprochen.

EIN viel wurde in den Internets über das Thema Schreiben von Modulen in C++ für Node.js (hier und hier). EIN
viel von
Abstraktionen wurden gebaut (hier und hier). Die meisten von ihnen
wird nicht in der Lage sein, die Robustheit und Prägnanz des zu übertreffen
offizielle Dokumente aber ich nehme es a
etwas langsamer und dokumentiere nur, wie ich auf einem sehr grundlegenden Niveau angekommen bin
Verständnis des Themas. Diese Erkundung ist natürlich nur eine sehr grundlegende
Ausgangspunkt mit vielen Details, die im weiteren Verlauf ausgelassen werden müssen.

Lebenszyklus eines Knotenmoduls

Zunächst müssen wir verstehen, dass wir, um C++ verwenden zu können, verstehen müssen
Code aus JavaScript, müssen wir den C++-Code in eine spezielle Binärdatei kompilieren lassen
Datei. Diese Dateien enden mit .node Erweiterung und sie enthalten eine Low-Level
Darstellung eines Node.js-Moduls. Knoten require() Funktion weiß, wie es geht
Behandeln Sie sie richtig und ein richtig kompiliertes C++-Modul funktioniert einfach
Kasten.

So sieht eine manuelle Anforderung aus:

const nativeModule = require('./build/Release/native')

In diesem Fall wird das Modul benannt native.node und sehr oft sind sie lokalisiert
in dem build/Release Ordner relativ zum Stammordner des Projekts. Mehr dazu
Ordnerstruktur später.

Ausführen der Hallo Welt

Wir beginnen mit dem obligatorischen Hallo Welt.

Sie benötigen ein C++-Toolkit, das bereits auf Ihrem System installiert ist (g++ auf Unix-ähnlich
Systeme und Visual Studio unter Windows). Weitere Einzelheiten können nachgelesen werden
node-gypREADME-Datei).

mkdir native-modules
cd native-modules
touch binding.gyp
touch package.json
touch main.js

touch main.cpp

Füllen package.json:

{
  "name": "node-native-modules-hello-world",
  "version": "0.0.1",
  "main": "index.js",
  "license": "MIT",
  "gypfile": true,
  "scripts": {
    "install": "node-gyp rebuild",
    "start": "node index.js"
  }
}

Putten node-gyp rebuild Befehl im install Skript stellt sicher, dass Ihre
Native Module werden bei jeder Ausführung kompiliert npm installdas ist
eigentlich ein Haken genannt und Hier ist mehr von
Sie. Mach dir keine Sorgen node-gyp binär, es ist heutzutage vorinstalliert
neben Node auf jedem System.

Dies node-gyp Binär ist eigentlich der Ort, an dem alle Bequemlichkeiten leben. Es ist ein sehr
intelligentes Dienstprogramm, das weiß, wie man Build-Systeme auf einer plattformübergreifenden Plattform generiert
Basis, je nachdem, wo es ausgeführt wird. Das ist eigentlich, woher sein Name kommt
aus: Gyp zum Generieren Sie Ihre Projekte und es hat seine Wurzeln aus der
Gyp Projekt des Chromium-Teams. Es weiß, wie es geht
Generieren Sie ein Visual Studio-Projekt unter Windows und a
machen-basierten Prozess auf Unix, aber wir sind
Ich gehe hier ins Detail und möchte wirklich alles einfach halten.

Das nächste wichtige Bit ist die gypfile: true Flagge in unserem package.json Datei.
Darauf weist es hin node-gyp sollte die nehmen binding.gyp Datei, die wir
bereits erstellt, berücksichtigt. Hier ist, was wir diese Datei füllen werden
mit:

{
  "targets": [
    {
      "target_name": "native",
      "sources": [ "main.cpp" ]
    }
  ]
}

Hier geben wir an, dass wir beabsichtigen, a zu generieren native.node Modul und es sollte
das Ergebnis der Kompilierung sein main.cpp.

Hier ist, was für unser Beispiel auf der C++-Seite der Dinge ausreicht (geben Sie das ein
main.cpp):

#include <node.h>

void HelloWorld(const v8::FunctionCallbackInfo<v8::Value>& args)
{
  v8::Isolate* isolate = args.GetIsolate();
  auto message =
    v8::String::NewFromUtf8(isolate, "Hello from the native side!");
  args.GetReturnValue().Set(message);
}

void Initialize(v8::Local<v8::Object> exports)
{
  NODE_SET_METHOD(exports, "helloWorld", HelloWorld);
}

NODE_MODULE(module_name, Initialize)

Dies wird Ihnen sehr bekannt vorkommen, wenn Sie über Kenntnisse in C++ verfügen. Hier
Wir definieren eine Funktion namens HelloWorld das gibt nur einen String zurück. Nächsten wir
erklären die helloWorld Eigentum auf die exports Objekt, um den Wert zu haben
HelloWorld. Dies führt effektiv zu einem Modul, das eine Funktion exportiert,
die eine grundlegende Zeichenfolge zurückgibt. Das ist der entsprechende JS-Code:

function HelloWorld() {
  return 'Hello from the native side!'
}

module.exports.helloWorld = HelloWorld

Jetzt haben wir die Aufgabe, dieses Stück Code in eine zu kompilieren native.node Datei.

npm install
ls build/Release

Sie können sehen, dass es a erzeugt hat ./build/Release/native.node Datei, bei der es sich um eine
Modul, das darauf wartet, dass wir es anfordern und verwenden!

Jetzt werden wir fortfahren und dieses Modul verwenden (geben Sie das ein main.js):

let native = require('./build/Release/native.node')

console.log(native.helloWorld())

Weil die native.node Modul bereits kompiliert ist, können wir sicher ausführen main.js
Datei und sehen Sie zu, wie sie läuft:

node main.js
Hello from the native side!

Das require(...) Teil sieht ein bisschen hässlich aus, aber wir können es sehr einfach mit dem beheben
Hilfe eines sehr kleinen npm-Moduls namens
bindings.

npm install bindings

Und nutzen Sie das Modul gleich. Hier ist das Ergebnis main.js Datei:

let native = require('bindings')('native')
console.log(native.helloWorld())

Viel einfacher und keine Notwendigkeit, den Pfad manuell zu verfolgen native.node Datei!
bindings wird die schwere Arbeit für uns erledigen.

Ein etwas komplexeres Beispiel

Als Nächstes führen wir eine Berechnung durch, die in C++ etwas komplizierter ist
nur um zu beweisen, dass wir in die richtige Richtung gehen.

Wir werden eine Funktion in C++ erstellen, die eine variable Menge von benötigt
Argumente und drucken Sie sie mit dem berühmten printf() Funktion. Der Trick ist
Übergeben Sie nur Zahlen aus JavaScript und geben Sie jede Zahl in möglichst vielen Basissystemen aus
wie möglich. Wir werden mit 2, 6, 7, 8 und 16 Basen umgehen. Das wird sein
genug für uns, um gefährlich genug zu werden.

Die Ordnerstruktur, die wir verwenden werden:

├── binding.gyp
├── build
│   ├── Makefile
│   ├── Release
│   ├── binding.Makefile
│   ├── config.gypi
│   ├── gyp-mac-tool
│   └── native.target.mk
├── main.cpp # C++ code for actually outputting formatted strings
├── main.js # the JS source code for running the program
└── package.json

4 directories, 11 files

Hier ist der eigentliche C++-Code, der die Logik zum Konvertieren von Zahlen implementiert:

void NativePrintf(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    int number = (int) args[0]->NumberValue();
    std::cout << "Base 10: ";
    convertDecimalToOtherBase(number, 10);
    std::cout << std::endl;
    std::cout << "Base 2: ";
    convertDecimalToOtherBase(number, 2);
    std::cout << std::endl;
    std::cout << "Base 6: ";
    convertDecimalToOtherBase(number, 6);
    std::cout << std::endl;
    std::cout << "Base 7: ";
    convertDecimalToOtherBase(number, 7);
    std::cout << std::endl;
    std::cout << "Base 8: ";
    convertDecimalToOtherBase(number, 8);
    std::cout << std::endl;
    std::cout << "Base 16: ";
    convertDecimalToOtherBase(number, 16);
    std::cout << std::endl;
    std::cout << "-------------";
    std::cout << std::endl;
}

Die Funktion convertDecimalToOtherBase() wird der Kürze halber weggelassen.

Den vollständigen Quellcode für das Beispiel finden Sie auf der
GitHub-Repository.

Wie Sie sehen können, können Sie mit ein wenig Hilfe von C++ ziemlich viel erreichen
komplexe Sachen sehr leicht. Sie können komplexe Apps implementieren, die Pipes starten oder
FIFOs und betten Sie sie vollständig in Ihre vorhandene Node-App ein, oder Sie können verwenden
beliebte Netzwerkbibliotheken für C++ in Ihr kleines Node-Programm. Das
Fantasie ist die Grenze.

Similar Posts

Leave a Reply

Your email address will not be published.