API d'intégration C++
Node.js fournit un certain nombre d'API C++ qui peuvent être utilisées pour exécuter 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. Outre les API exposées par Node.js, certains concepts requis sont fournis par l'API d'intégration V8.
Étant donné que l'utilisation de Node.js comme bibliothèque intégrée est différente de l'écriture de code exécuté par Node.js, les changements importants ne suivent pas la politique d'obsolescence typique de Node.js et peuvent se produire lors de chaque version majeure semver sans avertissement préalable.
Exemple d'application intégrée
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.
Mise en place 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 V8, telles qu'une instance
v8::Platform
.
L'exemple suivant montre comment ils peuvent être configurés. Certains noms de classe proviennent des espaces de noms C++ node
et v8
, respectivement.
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 imprimer toutes les erreurs survenues lors de la tentative d'analyse.
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 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
Configuration d'un état par instance
[Historique]
Version | Changements |
---|---|
v15.0.0 | Les utilitaires CommonEnvironmentSetup et SpinEventLoop ont été ajoutés. |
Node.js a un concept d'« instance Node.js », communément appelée node::Environment
. Chaque node::Environment
est associé à :
- Exactement un
v8::Isolate
, c'est-à-dire une instance du 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 pouvant ê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 mineures de performance lorsque les addons utilisent l'API C++ Buffer
de Node.js, et est nécessaire afin de suivre la mémoire ArrayBuffer
dans process.memoryUsage()
.
De plus, chaque v8::Isolate
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;
// Configure une boucle d'événements libuv, v8::Isolate et un environnement 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());
// Configure l'instance Node.js pour l'exécution et exécute du code à l'intérieur.
// Il existe également une variante qui prend un rappel et lui fournit
// les objets `require` et `process`, afin qu'il puisse compiler manuellement
// et exécuter les scripts au besoin.
// La fonction `require` à l'intérieur de ce script n'accède *pas* au
// système de fichiers et ne peut charger que des modules Node.js intégrés.
// `module.createRequire()` est utilisé pour en créer un qui est 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 de davantage de 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
56