Skip to content

API C++ per l'incorporamento

Node.js fornisce una serie di API C++ che possono essere utilizzate per eseguire JavaScript in un ambiente Node.js da un altro software C++.

La documentazione per queste API è disponibile in src/node.h nell'albero dei sorgenti di Node.js. Oltre alle API esposte da Node.js, alcuni concetti richiesti sono forniti dall'API di incorporamento V8.

Poiché l'utilizzo di Node.js come libreria incorporata è diverso dalla scrittura di codice che viene eseguito da Node.js, le modifiche che causano interruzioni non seguono la tipica politica di deprecazione di Node.js e possono verificarsi in ogni versione semver-major senza preavviso.

Esempio di applicazione di incorporamento

Le sezioni seguenti forniranno una panoramica su come utilizzare queste API per creare un'applicazione da zero che eseguirà l'equivalente di node -e \<code\>, ovvero che prenderà un pezzo di JavaScript ed eseguirà in un ambiente specifico per Node.js.

Il codice completo può essere trovato nell'albero dei sorgenti di Node.js.

Impostazione di uno stato per processo

Node.js richiede una gestione dello stato per processo per poter essere eseguito:

  • Analisi degli argomenti per le opzioni CLI di Node.js,
  • Requisiti per processo V8, come un'istanza v8::Platform.

L'esempio seguente mostra come questi possono essere impostati. Alcuni nomi di classe provengono rispettivamente dagli spazi dei nomi C++ node e v8.

C++
int main(int argc, char** argv) {
  argv = uv_setup_args(argc, argv);
  std::vector<std::string> args(argv, argv + argc);
  // Analizza le opzioni CLI di Node.js e stampa eventuali errori che si sono verificati durante
  // il tentativo di analizzarle.
  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();
  }

  // Crea un'istanza v8::Platform. `MultiIsolatePlatform::Create()` è un modo
  // per creare un'istanza v8::Platform che Node.js può utilizzare quando crea
  // thread Worker. Quando non è presente alcuna istanza `MultiIsolatePlatform`,
  // i thread Worker sono disabilitati.
  std::unique_ptr<MultiIsolatePlatform> platform =
      MultiIsolatePlatform::Create(4);
  V8::InitializePlatform(platform.get());
  V8::Initialize();

  // Vedere sotto per il contenuto di questa funzione.
  int ret = RunNodeInstance(
      platform.get(), result->args(), result->exec_args());

  V8::Dispose();
  V8::DisposePlatform();

  node::TearDownOncePerProcess();
  return ret;
}

Impostazione di uno stato per istanza

[Cronologia]

VersioneModifiche
v15.0.0Sono state aggiunte le utility CommonEnvironmentSetup e SpinEventLoop.

Node.js ha un concetto di "istanza Node.js", che comunemente viene indicato come node::Environment. Ciascun node::Environment è associato a:

  • Esattamente un v8::Isolate, ovvero un'istanza del motore JS,
  • Esattamente un uv_loop_t, ovvero un event loop,
  • Un numero di v8::Context, ma esattamente un v8::Context principale, e
  • Un'istanza node::IsolateData che contiene informazioni che potrebbero essere condivise da più node::Environment. L'incorporatore deve assicurarsi che node::IsolateData sia condiviso solo tra node::Environment che utilizzano lo stesso v8::Isolate, Node.js non esegue questo controllo.

Per impostare un v8::Isolate, è necessario fornire un v8::ArrayBuffer::Allocator. Una possibile scelta è l'allocatore predefinito di Node.js, che può essere creato tramite node::ArrayBufferAllocator::Create(). L'utilizzo dell'allocatore Node.js consente piccole ottimizzazioni delle prestazioni quando gli addon utilizzano l'API Buffer C++ di Node.js ed è necessario per tracciare la memoria ArrayBuffer in process.memoryUsage().

Inoltre, ogni v8::Isolate che viene utilizzato per un'istanza Node.js deve essere registrato e deregistrato con l'istanza MultiIsolatePlatform, se ne viene utilizzata una, affinché la piattaforma sappia quale event loop utilizzare per le attività pianificate da v8::Isolate.

La funzione helper node::NewIsolate() crea un v8::Isolate, lo imposta con alcuni hook specifici di Node.js (ad esempio il gestore di errori di Node.js) e lo registra automaticamente con la piattaforma.

C++
int RunNodeInstance(MultiIsolatePlatform* platform,
                    const std::vector<std::string>& args,
                    const std::vector<std::string>& exec_args) {
  int exit_code = 0;

  // Imposta un event loop libuv, v8::Isolate e Node.js Environment.
  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);
    // Il v8::Context deve essere immesso quando vengono chiamati node::CreateEnvironment() e
    // node::LoadEnvironment().
    Context::Scope context_scope(setup->context());

    // Imposta l'istanza Node.js per l'esecuzione ed esegue il codice al suo interno.
    // Esiste anche una variante che accetta un callback e lo fornisce con
    // gli oggetti `require` e `process`, in modo che possa compilare manualmente
    // ed eseguire gli script secondo necessità.
    // La funzione `require` all'interno di questo script non accede al file
    // system e può caricare solo moduli Node.js integrati.
    // `module.createRequire()` viene utilizzato per crearne uno in grado di
    // caricare file dal disco e utilizza il caricatore di file CommonJS standard
    // invece della funzione `require` solo interna.
    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())  // Si è verificata un'eccezione JS.
      return 1;

    exit_code = node::SpinEventLoop(env).FromMaybe(1);

    // node::Stop() può essere utilizzato per arrestare esplicitamente l'event loop e impedire
    // l'ulteriore esecuzione di JavaScript. Può essere chiamato da qualsiasi thread,
    // e si comporterà come worker.terminate() se chiamato da un altro thread.
    node::Stop(env);
  }

  return exit_code;
}