C++ Embedder-API
Node.js bietet eine Reihe von C++-APIs, die verwendet werden können, um JavaScript in einer Node.js-Umgebung aus anderer C++-Software auszuführen.
Die Dokumentation für diese APIs findet sich in src/node.h im Node.js-Quellbaum. Zusätzlich zu den von Node.js bereitgestellten APIs werden einige benötigte Konzepte von der V8 Embedder-API bereitgestellt.
Da die Verwendung von Node.js als eingebettete Bibliothek sich von der Erstellung von Code unterscheidet, der von Node.js ausgeführt wird, folgen Breaking Changes nicht der typischen Node.js Deprecation-Richtlinie und können ohne vorherige Ankündigung bei jedem semver-Hauptrelease auftreten.
Beispiel für eine eingebettete Anwendung
Die folgenden Abschnitte geben einen Überblick darüber, wie man diese APIs verwendet, um von Grund auf eine Anwendung zu erstellen, die das Äquivalent von node -e <code\>
ausführt, d. h. ein Stück JavaScript entgegennimmt und es in einer Node.js-spezifischen Umgebung ausführt.
Der vollständige Code findet sich im Node.js-Quellbaum.
Einrichten eines prozessübergreifenden Zustands
Node.js benötigt eine gewisse prozessübergreifende Zustandsverwaltung, um ausgeführt werden zu können:
- Argumentparsing für Node.js CLI-Optionen,
- V8-Prozessanforderungen, wie z. B. eine
v8::Platform
-Instanz.
Das folgende Beispiel zeigt, wie diese eingerichtet werden können. Einige Klassennamen stammen aus den C++-Namespaces node
bzw. v8
.
int main(int argc, char** argv) {
argv = uv_setup_args(argc, argv);
std::vector<std::string> args(argv, argv + argc);
// Node.js CLI-Optionen parsen und alle Fehler ausgeben, die beim
// Parsen aufgetreten sind.
std::unique_ptr<node::InitializationResult> result =
node::InitializeOncePerProcess(args, {
node::ProcessInitializationFlags::kNoInitializeV8,
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform
});
for (const std::string& error : result->errors())
fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
if (result->early_return() != 0) {
return result->exit_code();
}
// Eine v8::Platform-Instanz erstellen. `MultiIsolatePlatform::Create()` ist eine Möglichkeit,
// eine v8::Platform-Instanz zu erstellen, die Node.js bei der Erstellung
// von Worker-Threads verwenden kann. Wenn keine `MultiIsolatePlatform`-Instanz vorhanden ist,
// sind Worker-Threads deaktiviert.
std::unique_ptr<MultiIsolatePlatform> platform =
MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();
// Siehe unten für den Inhalt dieser Funktion.
int ret = RunNodeInstance(
platform.get(), result->args(), result->exec_args());
V8::Dispose();
V8::DisposePlatform();
node::TearDownOncePerProcess();
return ret;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Einrichtung eines instanzspezifischen Zustands
[Verlauf]
Version | Änderungen |
---|---|
v15.0.0 | Die Dienstprogramme CommonEnvironmentSetup und SpinEventLoop wurden hinzugefügt. |
Node.js hat das Konzept einer „Node.js-Instanz“, die allgemein als node::Environment
bezeichnet wird. Jede node::Environment
ist verknüpft mit:
- Genau einem
v8::Isolate
, d. h. einer JS-Engine-Instanz, - Genau einem
uv_loop_t
, d. h. einer Ereignisschleife, - Mehreren
v8::Context
s, aber genau einem Haupt-v8::Context
, und - Einer
node::IsolateData
-Instanz, die Informationen enthält, die von mehrerennode::Environment
s geteilt werden können. Der Einbetter sollte sicherstellen, dassnode::IsolateData
nur zwischennode::Environment
s geteilt wird, die dasselbev8::Isolate
verwenden. Node.js führt diese Prüfung nicht durch.
Um ein v8::Isolate
einzurichten, muss ein v8::ArrayBuffer::Allocator
bereitgestellt werden. Eine mögliche Wahl ist der Standard-Node.js-Allokator, der über node::ArrayBufferAllocator::Create()
erstellt werden kann. Die Verwendung des Node.js-Allokators ermöglicht geringfügige Leistungsoptimierungen, wenn Addons die Node.js C++ Buffer
-API verwenden, und ist erforderlich, um den ArrayBuffer
-Speicher in process.memoryUsage()
zu verfolgen.
Zusätzlich muss jedes v8::Isolate
, das für eine Node.js-Instanz verwendet wird, bei der MultiIsolatePlatform
-Instanz registriert und deregistriert werden, falls eine verwendet wird, damit die Plattform weiß, welche Ereignisschleife für Aufgaben verwendet werden soll, die vom v8::Isolate
geplant werden.
Die Hilfsfunktion node::NewIsolate()
erstellt ein v8::Isolate
, richtet es mit einigen Node.js-spezifischen Hooks (z. B. dem Node.js-Fehlerhandler) ein und registriert es automatisch bei der Plattform.
int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
// Einrichtung einer libuv-Ereignisschleife, v8::Isolate und Node.js-Umgebung.
std::vector<std::string> errors;
std::unique_ptr<CommonEnvironmentSetup> setup =
CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
if (!setup) {
for (const std::string& err : errors)
fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
return 1;
}
Isolate* isolate = setup->isolate();
Environment* env = setup->env();
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
// Der v8::Context muss eingegeben werden, wenn node::CreateEnvironment() und
// node::LoadEnvironment() aufgerufen werden.
Context::Scope context_scope(setup->context());
// Einrichtung der Node.js-Instanz zur Ausführung und Ausführung von Code darin.
// Es gibt auch eine Variante, die einen Rückruf entgegennimmt und diesem die
// Objekte `require` und `process` zur Verfügung stellt, sodass sie Skripte
// nach Bedarf manuell kompilieren und ausführen kann.
// Die Funktion `require` in diesem Skript greift *nicht* auf das Dateisystem zu
// und kann nur integrierte Node.js-Module laden.
// `module.createRequire()` wird verwendet, um eine zu erstellen, die in der Lage ist,
// Dateien von der Festplatte zu laden und den standardmäßigen CommonJS-Dateilader
// anstelle der internen `require`-Funktion verwendet.
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
env,
"const publicRequire ="
" require('node:module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
"require('node:vm').runInThisContext(process.argv[1]);");
if (loadenv_ret.IsEmpty()) // Es gab eine JS-Ausnahme.
return 1;
exit_code = node::SpinEventLoop(env).FromMaybe(1);
// node::Stop() kann verwendet werden, um die Ereignisschleife explizit zu stoppen und
// die weitere Ausführung von JavaScript zu verhindern. Sie kann von jedem Thread aufgerufen werden
// und verhält sich wie worker.terminate(), wenn sie von einem anderen Thread aufgerufen wird.
node::Stop(env);
}
return exit_code;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55