API C++ d'intégration
Node.js fournit un certain nombre d'API C++ qui peuvent être utilisées pour exécuter du JavaScript dans un environnement Node.js à partir d'autres logiciels C++.
La documentation de ces API se trouve dans src/node.h dans l'arborescence des sources de Node.js. En plus des API exposées par Node.js, certains concepts requis sont fournis par l'API d'intégration V8.
Comme l'utilisation de Node.js en tant que bibliothèque intégrée est différente de l'écriture de code exécuté par Node.js, les changements cassants ne suivent pas la politique de dépréciation typique de Node.js et peuvent se produire à chaque version semver-major sans avertissement préalable.
Exemple d'application d'intégration
Les sections suivantes donneront un aperçu de la façon d'utiliser ces API pour créer une application à partir de zéro qui effectuera l'équivalent de node -e \<code\>
, c'est-à-dire qui prendra un morceau de JavaScript et l'exécutera dans un environnement spécifique à Node.js.
Le code complet se trouve dans l'arborescence des sources de Node.js.
Configuration d'un état par processus
Node.js nécessite une certaine gestion de l'état par processus pour fonctionner :
- Analyse des arguments pour les options CLI de Node.js,
- Exigences par processus de V8, telles qu'une instance
v8::Platform
.
L'exemple suivant montre comment ces éléments peuvent être configurés. Certains noms de classes proviennent respectivement des espaces de noms C++ node
et v8
.
int main(int argc, char** argv) {
argv = uv_setup_args(argc, argv);
std::vector<std::string> args(argv, argv + argc);
// Analyser les options CLI de Node.js et afficher les erreurs qui se sont produites lors de la tentative
// de les analyser.
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();
}
// Créer une instance v8::Platform. `MultiIsolatePlatform::Create()` est un moyen
// de créer une instance v8::Platform que Node.js peut utiliser lors de la création
// de threads Worker. Lorsqu'aucune instance `MultiIsolatePlatform` n'est présente,
// les threads Worker sont désactivés.
std::unique_ptr<MultiIsolatePlatform> platform =
MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();
// Voir ci-dessous pour le contenu de cette fonction.
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
Configuration d'un état par instance
[Historique]
Version | Modifications |
---|---|
v15.0.0 | Les utilitaires CommonEnvironmentSetup et SpinEventLoop ont été ajoutés. |
Node.js a un concept d'"instance Node.js", qui est communément appelé node::Environment
. Chaque node::Environment
est associé à :
- Exactement un
v8::Isolate
, c'est-à-dire une instance de moteur JS, - Exactement un
uv_loop_t
, c'est-à-dire une boucle d'événements, - Un certain nombre de
v8::Context
, mais exactement unv8::Context
principal, et - Une instance
node::IsolateData
qui contient des informations qui pourraient être partagées par plusieursnode::Environment
. L'intégrateur doit s'assurer quenode::IsolateData
n'est partagé qu'entre lesnode::Environment
qui utilisent le mêmev8::Isolate
, Node.js n'effectue pas cette vérification.
Afin de configurer un v8::Isolate
, un v8::ArrayBuffer::Allocator
doit être fourni. Un choix possible est l'allocateur Node.js par défaut, qui peut être créé via node::ArrayBufferAllocator::Create()
. L'utilisation de l'allocateur Node.js permet des optimisations de performance mineures lorsque les addons utilisent l'API C++ Buffer
de Node.js, et est nécessaire pour suivre la mémoire ArrayBuffer
dans process.memoryUsage()
.
De plus, chaque v8::Isolate
qui est utilisé pour une instance Node.js doit être enregistré et désenregistré auprès de l'instance MultiIsolatePlatform
, si elle est utilisée, afin que la plateforme sache quelle boucle d'événements utiliser pour les tâches planifiées par le v8::Isolate
.
La fonction d'assistance node::NewIsolate()
crée un v8::Isolate
, le configure avec des hooks spécifiques à Node.js (par exemple, le gestionnaire d'erreurs Node.js) et l'enregistre automatiquement auprès de la plateforme.
int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
// Configuration d'une boucle d'événements libuv, v8::Isolate, et Environment Node.js.
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);
// Le v8::Context doit être entré lorsque node::CreateEnvironment() et
// node::LoadEnvironment() sont appelés.
Context::Scope context_scope(setup->context());
// Configurez l'instance Node.js pour l'exécution et exécutez le code à l'intérieur.
// Il existe également une variante qui prend un callback et lui fournit
// les objets `require` et `process`, afin qu'il puisse compiler manuellement
// et exécuter des scripts selon les besoins.
// La fonction `require` à l'intérieur de ce script n'accède *pas* au système de fichiers,
// et ne peut charger que les modules Node.js intégrés.
// `module.createRequire()` est utilisé pour en créer un capable de
// charger des fichiers à partir du disque, et utilise le chargeur de fichiers CommonJS standard
// au lieu de la fonction `require` interne uniquement.
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()) // Il y a eu une exception JS.
return 1;
exit_code = node::SpinEventLoop(env).FromMaybe(1);
// node::Stop() peut être utilisé pour arrêter explicitement la boucle d'événements et empêcher
// l'exécution d'autres JavaScript. Il peut être appelé depuis n'importe quel thread,
// et agira comme worker.terminate() s'il est appelé depuis un autre thread.
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