Node.js v20.11.1 文档


C++ 插件#

¥C++ addons

插件是用 C++ 编写的动态链接共享对象。require() 函数可以将插件加载为普通的 Node.js 模块。插件提供了 JavaScript 和 C/C++ 库之间的接口。

¥Addons are dynamically-linked shared objects written in C++. The require() function can load addons as ordinary Node.js modules. Addons provide an interface between JavaScript and C/C++ libraries.

实现插件有三种选择:Node-API、nan,或直接使用内部 V8、libuv 和 Node.js 库。除非需要直接访问 Node-API 未暴露的功能,否则请使用 Node-API。有关 Node-API 的更多信息,请参阅 使用 Node-API 的 C/C++ 插件

¥There are three options for implementing addons: Node-API, nan, or direct use of internal V8, libuv, and Node.js libraries. Unless there is a need for direct access to functionality which is not exposed by Node-API, use Node-API. Refer to C/C++ addons with Node-API for more information on Node-API.

不使用 Node-API 时,实现插件很复杂,涉及若干组件和 API 的知识:

¥When not using Node-API, implementing addons is complicated, involving knowledge of several components and APIs:

  • V8:Node.js 用来提供 JavaScript 实现的 C++ 库。V8 提供了创建对象、调用函数等机制。V8 的 API 主要记录在 v8.h 头文件(Node.js 源代码树中的 deps/v8/include/v8.h),在线 也可用。

    ¥V8: the C++ library Node.js uses to provide the JavaScript implementation. V8 provides the mechanisms for creating objects, calling functions, etc. V8's API is documented mostly in the v8.h header file (deps/v8/include/v8.h in the Node.js source tree), which is also available online.

  • libuv:实现 Node.js 事件循环、其工作线程和平台所有异步行为的 C 库。它还充当跨平台抽象库,在所有主要的操作系统上都可以轻松、类似于 POSIX 的访问,例如与文件系统、套接字、定时器、以及系统事件进行交互。libuv 还提供类似于 POSIX 线程的线程抽象,用于需要超越标准事件循环的更复杂的异步插件。插件作者应该避免使用 I/O 或其他时间密集型任务阻塞事件循环,通过将工作通过 libuv 分流到非阻塞系统操作、工作线程、或 libuv 线程的自定义使用来实现。

    ¥libuv: The C library that implements the Node.js event loop, its worker threads and all of the asynchronous behaviors of the platform. It also serves as a cross-platform abstraction library, giving easy, POSIX-like access across all major operating systems to many common system tasks, such as interacting with the file system, sockets, timers, and system events. libuv also provides a threading abstraction similar to POSIX threads for more sophisticated asynchronous addons that need to move beyond the standard event loop. Addon authors should avoid blocking the event loop with I/O or other time-intensive tasks by offloading work via libuv to non-blocking system operations, worker threads, or a custom use of libuv threads.

  • 内部 Node.js 库。Node.js 自身导出了插件可以使用的 C++ API,其中最重要的是 node::ObjectWrap 类。

    ¥Internal Node.js libraries. Node.js itself exports C++ APIs that addons can use, the most important of which is the node::ObjectWrap class.

  • Node.js 包括了其他静态链接库,包括 OpenSSL。这些其他库位于 Node.js 源代码树的 deps/ 目录中。只有 libuv、OpenSSL、V8 和 zlib 符号被 Node.js 有目的地重新导出,并且可以被插件在不同程度上使用。有关其他信息,请参阅 链接到 Node.js 中包含的库

    ¥Node.js includes other statically linked libraries including OpenSSL. These other libraries are located in the deps/ directory in the Node.js source tree. Only the libuv, OpenSSL, V8, and zlib symbols are purposefully re-exported by Node.js and may be used to various extents by addons. See Linking to libraries included with Node.js for additional information.

以下所有示例均适用于 下载,可用作插件的起点。

¥All of the following examples are available for download and may be used as the starting-point for an addon.

你好世界#

¥Hello world

这个 "你好世界" 示例是一个简单的插件,用 C++ 编写,相当于以下 JavaScript 代码:

¥This "Hello world" example is a simple addon, written in C++, that is the equivalent of the following JavaScript code:

module.exports.hello = () => 'world'; 

首先,创建文件 hello.cc

¥First, create the file hello.cc:

// hello.cc
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(
      isolate, "world").ToLocalChecked());
}

void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

}  // namespace demo 

所有 Node.js 插件都必须按照以下模式导出初始化函数:

¥All Node.js addons must export an initialization function following the pattern:

void Initialize(Local<Object> exports);
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) 

NODE_MODULE 后面没有分号,因为它不是函数(参见 node.h)。

¥There is no semi-colon after NODE_MODULE as it's not a function (see node.h).

module_name 必须与最终二进制文件的文件名匹配(不包括 .node 后缀)。

¥The module_name must match the filename of the final binary (excluding the .node suffix).

hello.cc 示例中,初始化函数为 Initialize,插件模块名称为 addon

¥In the hello.cc example, then, the initialization function is Initialize and the addon module name is addon.

使用 node-gyp 构建插件时,使用宏 NODE_GYP_MODULE_NAME 作为 NODE_MODULE() 的第一个参数将确保最终二进制文件的名称将传给 NODE_MODULE()

¥When building addons with node-gyp, using the macro NODE_GYP_MODULE_NAME as the first parameter of NODE_MODULE() will ensure that the name of the final binary will be passed to NODE_MODULE().

NODE_MODULE() 定义的插件不能同时加载在多个上下文或多个线程中。

¥Addons defined with NODE_MODULE() can not be loaded in multiple contexts or multiple threads at the same time.

上下文感知的插件#

¥Context-aware addons

在某些环境中,可能需要在多个上下文中多次加载 Node.js 插件。例如,Electron 运行时在单个进程中运行多个 Node.js 实例。每个实例都有自己的 require() 缓存,因此当通过 require() 加载时,每个实例都需要原生插件才能正确运行。这意味着插件必须支持多个初始化。

¥There are environments in which Node.js addons may need to be loaded multiple times in multiple contexts. For example, the Electron runtime runs multiple instances of Node.js in a single process. Each instance will have its own require() cache, and thus each instance will need a native addon to behave correctly when loaded via require(). This means that the addon must support multiple initializations.

可以使用宏 NODE_MODULE_INITIALIZER 构建上下文感知插件,该宏扩展为 Node.js 在加载插件时期望找到的函数的名称。因此可以像下面的示例一样初始化插件:

¥A context-aware addon can be constructed by using the macro NODE_MODULE_INITIALIZER, which expands to the name of a function which Node.js will expect to find when it loads an addon. An addon can thus be initialized as in the following example:

using namespace v8;

extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
                        Local<Value> module,
                        Local<Context> context) {
  /* Perform addon initialization steps here. */
} 

另一种选择是使用宏 NODE_MODULE_INIT(),它也将构建上下文感知插件。与 NODE_MODULE() 不同,NODE_MODULE() 用于围绕给定的 addon 初始化函数构造插件,而 NODE_MODULE_INIT() 用作此类初始化器的声明,然后是函数体。

¥Another option is to use the macro NODE_MODULE_INIT(), which will also construct a context-aware addon. Unlike NODE_MODULE(), which is used to construct an addon around a given addon initializer function, NODE_MODULE_INIT() serves as the declaration of such an initializer to be followed by a function body.

在调用 NODE_MODULE_INIT() 之后,可以在函数体内使用以下三个变量:

¥The following three variables may be used inside the function body following an invocation of NODE_MODULE_INIT():

  • Local<Object> exports,

  • Local<Value> module,和

    ¥Local<Value> module, and

  • Local<Context> context

选择构建上下文感知插件承担着仔细管理全局静态数据的责任。由于插件可能被多次加载,甚至可能来自不同的线程,因此必须适当保护存储在插件中的任何全局静态数据,并且不得包含对 JavaScript 对象的任何持久引用。这样做的原因是 JavaScript 对象仅在上下文中有效,并且当从错误的上下文或从与创建它们的线程不同的线程访问时,可能会导致崩溃。

¥The choice to build a context-aware addon carries with it the responsibility of carefully managing global static data. Since the addon may be loaded multiple times, potentially even from different threads, any global static data stored in the addon must be properly protected, and must not contain any persistent references to JavaScript objects. The reason for this is that JavaScript objects are only valid in one context, and will likely cause a crash when accessed from the wrong context or from a different thread than the one on which they were created.

通过执行以下步骤,可以构造上下文感知插件以避免全局静态数据:

¥The context-aware addon can be structured to avoid global static data by performing the following steps:

  • 定义一个类,该类将保存每个插件实例数据并具有该形式的静态成员

    ¥Define a class which will hold per-addon-instance data and which has a static member of the form

    static void DeleteInstance(void* data) {
      // Cast `data` to an instance of the class and delete it.
    } 
  • 在插件初始值设定项中堆分配此类的实例。这可以使用 new 关键字来完成。

    ¥Heap-allocate an instance of this class in the addon initializer. This can be accomplished using the new keyword.

  • 调用 node::AddEnvironmentCleanupHook(),将上面创建的实例和指向 DeleteInstance() 的指针传给它。这将确保在拆除环境时删除实例。

    ¥Call node::AddEnvironmentCleanupHook(), passing it the above-created instance and a pointer to DeleteInstance(). This will ensure the instance is deleted when the environment is torn down.

  • 将类的实例存储在 v8::External 中,并且

    ¥Store the instance of the class in a v8::External, and

  • 通过将 v8::External 传给创建原生支持的 ​​JavaScript 函数的 v8::FunctionTemplate::New()v8::Function::New(),将 v8::External 传给所有暴露给 JavaScript 的方法。v8::FunctionTemplate::New()v8::Function::New() 的第三个参数接受 v8::External 并使其在使用 v8::FunctionCallbackInfo::Data() 方法的原生回调中可用。

    ¥Pass the v8::External to all methods exposed to JavaScript by passing it to v8::FunctionTemplate::New() or v8::Function::New() which creates the native-backed JavaScript functions. The third parameter of v8::FunctionTemplate::New() or v8::Function::New() accepts the v8::External and makes it available in the native callback using the v8::FunctionCallbackInfo::Data() method.

这将确保每个插件实例数据到达可以从 JavaScript 调用的每个绑定。每个插件实例数据还必须传入到插件可能创建的任何异步回调中。

¥This will ensure that the per-addon-instance data reaches each binding that can be called from JavaScript. The per-addon-instance data must also be passed into any asynchronous callbacks the addon may create.

以下示例说明了上下文感知插件的实现:

¥The following example illustrates the implementation of a context-aware addon:

#include <node.h>

using namespace v8;

class AddonData {
 public:
  explicit AddonData(Isolate* isolate):
      call_count(0) {
    // Ensure this per-addon-instance data is deleted at environment cleanup.
    node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
  }

  // Per-addon data.
  int call_count;

  static void DeleteInstance(void* data) {
    delete static_cast<AddonData*>(data);
  }
};

static void Method(const v8::FunctionCallbackInfo<v8::Value>& info) {
  // Retrieve the per-addon-instance data.
  AddonData* data =
      reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());
  data->call_count++;
  info.GetReturnValue().Set((double)data->call_count);
}

// Initialize this addon to be context-aware.
NODE_MODULE_INIT(/* exports, module, context */) {
  Isolate* isolate = context->GetIsolate();

  // Create a new instance of `AddonData` for this instance of the addon and
  // tie its life cycle to that of the Node.js environment.
  AddonData* data = new AddonData(isolate);

  // Wrap the data in a `v8::External` so we can pass it to the method we
  // expose.
  Local<External> external = External::New(isolate, data);

  // Expose the method `Method` to JavaScript, and make sure it receives the
  // per-addon-instance data we created above by passing `external` as the
  // third parameter to the `FunctionTemplate` constructor.
  exports->Set(context,
               String::NewFromUtf8(isolate, "method").ToLocalChecked(),
               FunctionTemplate::New(isolate, Method, external)
                  ->GetFunction(context).ToLocalChecked()).FromJust();
} 

工作线程支持#

¥Worker support

为了从多个 Node.js 环境(例如主线程和工作线程)加载,插件需要:

¥In order to be loaded from multiple Node.js environments, such as a main thread and a Worker thread, an add-on needs to either:

  • 成为 Node-API 插件,或

    ¥Be an Node-API addon, or

  • 如上所述使用 NODE_MODULE_INIT() 声明为上下文感知

    ¥Be declared as context-aware using NODE_MODULE_INIT() as described above

为了支持 Worker 线程,插件需要清理它们可能在此类线程存在时分配的任何资源。这可以通过使用 AddEnvironmentCleanupHook() 函数来实现:

¥In order to support Worker threads, addons need to clean up any resources they may have allocated when such a thread exists. This can be achieved through the usage of the AddEnvironmentCleanupHook() function:

void AddEnvironmentCleanupHook(v8::Isolate* isolate,
                               void (*fun)(void* arg),
                               void* arg); 

此函数添加了一个钩子,该钩子将在给定的 Node.js 实例关闭之前运行。如有必要,可以在使用具有相同签名的 RemoveEnvironmentCleanupHook() 运行这些钩子之前将其删除。回调按后进先出的顺序运行。

¥This function adds a hook that will run before a given Node.js instance shuts down. If necessary, such hooks can be removed before they are run using RemoveEnvironmentCleanupHook(), which has the same signature. Callbacks are run in last-in first-out order.

如有必要,还有一对额外的 AddEnvironmentCleanupHook()RemoveEnvironmentCleanupHook() 重载,其中清理钩子采用回调函数。这可用于关闭异步资源,例如插件注册的任何 libuv 句柄。

¥If necessary, there is an additional pair of AddEnvironmentCleanupHook() and RemoveEnvironmentCleanupHook() overloads, where the cleanup hook takes a callback function. This can be used for shutting down asynchronous resources, such as any libuv handles registered by the addon.

以下 addon.cc 使用 AddEnvironmentCleanupHook

¥The following addon.cc uses AddEnvironmentCleanupHook:

// addon.cc
#include <node.h>
#include <assert.h>
#include <stdlib.h>

using node::AddEnvironmentCleanupHook;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;

// Note: In a real-world application, do not rely on static/global data.
static char cookie[] = "yum yum";
static int cleanup_cb1_called = 0;
static int cleanup_cb2_called = 0;

static void cleanup_cb1(void* arg) {
  Isolate* isolate = static_cast<Isolate*>(arg);
  HandleScope scope(isolate);
  Local<Object> obj = Object::New(isolate);
  assert(!obj.IsEmpty());  // assert VM is still alive
  assert(obj->IsObject());
  cleanup_cb1_called++;
}

static void cleanup_cb2(void* arg) {
  assert(arg == static_cast<void*>(cookie));
  cleanup_cb2_called++;
}

static void sanity_check(void*) {
  assert(cleanup_cb1_called == 1);
  assert(cleanup_cb2_called == 1);
}

// Initialize this addon to be context-aware.
NODE_MODULE_INIT(/* exports, module, context */) {
  Isolate* isolate = context->GetIsolate();

  AddEnvironmentCleanupHook(isolate, sanity_check, nullptr);
  AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie);
  AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate);
} 

通过运行在 JavaScript 中进行测试:

¥Test in JavaScript by running:

// test.js
require('./build/Release/addon'); 

构建#

¥Building

编写源代码后,必须将其编译为二进制 addon.node 文件。为此,请在项目的顶层创建名为 binding.gyp 的文件,使用类似 JSON 的格式描述模块的构建配置。该文件由 node-gyp 使用,node-gyp 是专门为编译 Node.js 插件而编写的工具。

¥Once the source code has been written, it must be compiled into the binary addon.node file. To do so, create a file called binding.gyp in the top-level of the project describing the build configuration of the module using a JSON-like format. This file is used by node-gyp, a tool written specifically to compile Node.js addons.

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "hello.cc" ]
    }
  ]
} 

node-gyp 实用工具的一个版本作为 npm 的一部分与 Node.js 打包和分发。此版本不直接提供给开发者使用,仅旨在支持使用 npm install 命令编译和安装插件的能力。希望直接使用 node-gyp 的开发者可以使用命令 npm install -g node-gyp 安装它。有关详细信息,包括特定于平台的要求,请参阅 node-gyp 安装说明

¥A version of the node-gyp utility is bundled and distributed with Node.js as part of npm. This version is not made directly available for developers to use and is intended only to support the ability to use the npm install command to compile and install addons. Developers who wish to use node-gyp directly can install it using the command npm install -g node-gyp. See the node-gyp installation instructions for more information, including platform-specific requirements.

创建 binding.gyp 文件后,使用 node-gyp configure 为当前平台生成适当的项目构建文件。这将在 build/ 目录中生成 Makefile(在 Unix 平台上)或 vcxproj 文件(在 Windows 上)。

¥Once the binding.gyp file has been created, use node-gyp configure to generate the appropriate project build files for the current platform. This will generate either a Makefile (on Unix platforms) or a vcxproj file (on Windows) in the build/ directory.

接下来,调用 node-gyp build 命令生成编译后的 addon.node 文件。这将被放入 build/Release/ 目录。

¥Next, invoke the node-gyp build command to generate the compiled addon.node file. This will be put into the build/Release/ directory.

当使用 npm install 安装 Node.js 插件时,npm 使用它自己的 node-gyp 打包版本来执行相同的一组操作,按需为用户平台生成插件的编译版本。

¥When using npm install to install a Node.js addon, npm uses its own bundled version of node-gyp to perform this same set of actions, generating a compiled version of the addon for the user's platform on demand.

构建完成后,可以通过将 require() 指向构建的 addon.node 模块在 Node.js 中使用二进制插件:

¥Once built, the binary addon can be used from within Node.js by pointing require() to the built addon.node module:

// hello.js
const addon = require('./build/Release/addon');

console.log(addon.hello());
// Prints: 'world' 

因为编译插件二进制文件的确切路径可能会根据编译方式而有所不同(即有时它可能在 ./build/Debug/ 中),插件可以使用 bindings 包来加载编译模块。

¥Because the exact path to the compiled addon binary can vary depending on how it is compiled (i.e. sometimes it may be in ./build/Debug/), addons can use the bindings package to load the compiled module.

虽然 bindings 包实现在如何定位插件模块方面更为复杂,但它本质上使用了类似于以下内容的 try…catch 模式:

¥While the bindings package implementation is more sophisticated in how it locates addon modules, it is essentially using a try…catch pattern similar to:

try {
  return require('./build/Release/addon.node');
} catch (err) {
  return require('./build/Debug/addon.node');
} 

链接到 Node.js 中包含的库#

¥Linking to libraries included with Node.js

Node.js 使用静态链接库,例如 V8、libuv 和 OpenSSL。所有插件都需要链接到 V8,也可以链接到任何其他依赖。通常,这就像包含适当的 #include <...> 语句(例如 #include <v8.h>)一样简单,node-gyp 将自动定位适当的头文件。但是,有一些注意事项需要注意:

¥Node.js uses statically linked libraries such as V8, libuv, and OpenSSL. All addons are required to link to V8 and may link to any of the other dependencies as well. Typically, this is as simple as including the appropriate #include <...> statements (e.g. #include <v8.h>) and node-gyp will locate the appropriate headers automatically. However, there are a few caveats to be aware of:

  • node-gyp 运行时,它会检测 Node.js 的特定发布版本并下载完整的源代码压缩包或仅下载头文件。如果下载了完整的源代码,插件将可以完全访问完整的 Node.js 依赖集。但是,如果只下载 Node.js 头文件,则只有 Node.js 导出的符号可用。

    ¥When node-gyp runs, it will detect the specific release version of Node.js and download either the full source tarball or just the headers. If the full source is downloaded, addons will have complete access to the full set of Node.js dependencies. However, if only the Node.js headers are downloaded, then only the symbols exported by Node.js will be available.

  • node-gyp 可以使用指向本地 Node.js 源镜像的 --nodedir 标志运行。使用此选项,插件将可以访问完整的依赖集。

    ¥node-gyp can be run using the --nodedir flag pointing at a local Node.js source image. Using this option, the addon will have access to the full set of dependencies.

使用 require() 加载插件#

¥Loading addons using require()

已编译的插件二进制文件的文件扩展名是 .node(与 .dll.so 相反)。require() 函数用于查找具有 .node 文件扩展名的文件并将它们初始化为动态链接库。

¥The filename extension of the compiled addon binary is .node (as opposed to .dll or .so). The require() function is written to look for files with the .node file extension and initialize those as dynamically-linked libraries.

调用 require() 时,通常可以省略 .node 扩展名,Node.js 仍会找到并初始化插件。但是,有一个注意事项,Node.js 将首先尝试定位和加载碰巧共享相同基本名称的模块或 JavaScript 文件。例如,如果在与二进制 addon.node 相同的目录中有一个文件 addon.js,那么 require('addon') 将优先于 addon.js 文件并加载它。

¥When calling require(), the .node extension can usually be omitted and Node.js will still find and initialize the addon. One caveat, however, is that Node.js will first attempt to locate and load modules or JavaScript files that happen to share the same base name. For instance, if there is a file addon.js in the same directory as the binary addon.node, then require('addon') will give precedence to the addon.js file and load it instead.

Node.js 的原生抽象#

¥Native abstractions for Node.js

本文档中说明的每个示例都直接使用 Node.js 和 V8 API 来实现插件。从一个 V8 版本到下一个版本(以及一个主要的 Node.js 版本到下一个版本),V8 API 可能并且已经发生了巨大的变化。每次更改时,插件可能需要更新和重新编译才能继续运行。Node.js 发布计划旨在最小化此类更改的频率和影响,但 Node.js 几乎无法确保 V8 API 的稳定性。

¥Each of the examples illustrated in this document directly use the Node.js and V8 APIs for implementing addons. The V8 API can, and has, changed dramatically from one V8 release to the next (and one major Node.js release to the next). With each change, addons may need to be updated and recompiled in order to continue functioning. The Node.js release schedule is designed to minimize the frequency and impact of such changes but there is little that Node.js can do to ensure stability of the V8 APIs.

Node.js 的原生抽象(或 nan)提供了一组工具,建议插件开发者使用这些工具来保持 V8 和 Node.js 过去和未来版本之间的兼容性。有关如何使用它的说明,请参见 nan 示例

¥The Native Abstractions for Node.js (or nan) provide a set of tools that addon developers are recommended to use to keep compatibility between past and future releases of V8 and Node.js. See the nan examples for an illustration of how it can be used.

Node-API#

稳定性: 2 - 稳定的

¥Stability: 2 - Stable

Node-API 是用于构建原生插件的 API。它独立于底层 JavaScript 运行时(例如 V8),并作为 Node.js 自身的一部分进行维护。此 API 将在 Node.js 的各个版本中保持稳定的应用二进制接口 (ABI)。它旨在将插件与底层 JavaScript 引擎中的更改隔离开来,并允许为一个版本编译的模块无需重新编译即可在更高版本的 Node.js 上运行。使用本文档中概述的相同方法/工具构建/打包插件(node-gyp 等)。唯一的区别是原生代码使用的 API 集。不使用 V8 或 Node.js 的原生抽象 API,而是使用 Node-API 中可用的函数。

¥Node-API is an API for building native addons. It is independent from the underlying JavaScript runtime (e.g. V8) and is maintained as part of Node.js itself. This API will be Application Binary Interface (ABI) stable across versions of Node.js. It is intended to insulate addons from changes in the underlying JavaScript engine and allow modules compiled for one version to run on later versions of Node.js without recompilation. Addons are built/packaged with the same approach/tools outlined in this document (node-gyp, etc.). The only difference is the set of APIs that are used by the native code. Instead of using the V8 or Native Abstractions for Node.js APIs, the functions available in the Node-API are used.

创建和维护受益于 Node-API 提供的 ABI 稳定性的插件带有某些 实现的注意事项

¥Creating and maintaining an addon that benefits from the ABI stability provided by Node-API carries with it certain implementation considerations.

要在上面的 "你好世界" 示例中使用 Node-API,请将 hello.cc 的内容替换为以下内容。所有其他指令保持不变。

¥To use Node-API in the above "Hello world" example, replace the content of hello.cc with the following. All other instructions remain the same.

// hello.cc using Node-API
#include <node_api.h>

namespace demo {

napi_value Method(napi_env env, napi_callback_info args) {
  napi_value greeting;
  napi_status status;

  status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting);
  if (status != napi_ok) return nullptr;
  return greeting;
}

napi_value init(napi_env env, napi_value exports) {
  napi_status status;
  napi_value fn;

  status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
  if (status != napi_ok) return nullptr;

  status = napi_set_named_property(env, exports, "hello", fn);
  if (status != napi_ok) return nullptr;
  return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, init)

}  // namespace demo 

使用 Node-API 的 C/C++ 插件 中记录了可用的功能以及如何使用它们。

¥The functions available and how to use them are documented in C/C++ addons with Node-API.

插件示例#

¥Addon examples

以下是一些旨在帮助开发者入门的示例插件。这些示例使用 V8 API。请参阅在线 V8 参考手册 以获取有关各种 V8 调用的帮助,以及 V8 的 嵌入器指南 以获取对使用的几个概念(如句柄、作用域、函数模板等)的解释。

¥Following are some example addons intended to help developers get started. The examples use the V8 APIs. Refer to the online V8 reference for help with the various V8 calls, and V8's Embedder's Guide for an explanation of several concepts used such as handles, scopes, function templates, etc.

这些示例中的每一个都使用以下 binding.gyp 文件:

¥Each of these examples using the following binding.gyp file:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "addon.cc" ]
    }
  ]
} 

如果有多个 .cc 文件,只需将附加文件名添加到 sources 数组:

¥In cases where there is more than one .cc file, simply add the additional filename to the sources array:

"sources": ["addon.cc", "myexample.cc"] 

一旦 binding.gyp 文件准备就绪,就可以使用 node-gyp 配置和构建示例插件:

¥Once the binding.gyp file is ready, the example addons can be configured and built using node-gyp:

node-gyp configure build 

函数参数#

¥Function arguments

插件通常会暴露可以从 Node.js 中运行的 JavaScript 访问的对象和函数。当从 JavaScript 调用函数时,输入参数和返回值必须映射到 C/C++ 代码和从 C/C++ 代码映射。

¥Addons will typically expose objects and functions that can be accessed from JavaScript running within Node.js. When functions are invoked from JavaScript, the input arguments and return value must be mapped to and from the C/C++ code.

以下示例说明了如何读取从 JavaScript 传入的函数参数以及如何返回结果:

¥The following example illustrates how to read function arguments passed from JavaScript and how to return a result:

// addon.cc
#include <node.h>

namespace demo {

using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

// This is the implementation of the "add" method
// Input arguments are passed using the
// const FunctionCallbackInfo<Value>& args struct
void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  // Check the number of arguments passed.
  if (args.Length() < 2) {
    // Throw an Error that is passed back to JavaScript
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate,
                            "Wrong number of arguments").ToLocalChecked()));
    return;
  }

  // Check the argument types
  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate,
                            "Wrong arguments").ToLocalChecked()));
    return;
  }

  // Perform the operation
  double value =
      args[0].As<Number>()->Value() + args[1].As<Number>()->Value();
  Local<Number> num = Number::New(isolate, value);

  // Set the return value (using the passed in
  // FunctionCallbackInfo<Value>&)
  args.GetReturnValue().Set(num);
}

void Init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "add", Add);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo 

编译后,可以在 Node.js 中加载和使用示例插件:

¥Once compiled, the example addon can be required and used from within Node.js:

// test.js
const addon = require('./build/Release/addon');

console.log('This should be eight:', addon.add(3, 5)); 

回调#

¥Callbacks

插件中的常见做法是将 JavaScript 函数传给 C++ 函数并从那里执行它们。以下示例说明了如何调用此类回调:

¥It is common practice within addons to pass JavaScript functions to a C++ function and execute them from there. The following example illustrates how to invoke such callbacks:

// addon.cc
#include <node.h>

namespace demo {

using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Null;
using v8::Object;
using v8::String;
using v8::Value;

void RunCallback(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();
  Local<Function> cb = Local<Function>::Cast(args[0]);
  const unsigned argc = 1;
  Local<Value> argv[argc] = {
      String::NewFromUtf8(isolate,
                          "hello world").ToLocalChecked() };
  cb->Call(context, Null(isolate), argc, argv).ToLocalChecked();
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", RunCallback);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo 

此示例使用 Init() 的双参数形式,它接收完整的 module 对象作为第二个参数。这允许插件使用单个函数完全覆盖 exports,而不是将该函数添加为 exports 的属性。

¥This example uses a two-argument form of Init() that receives the full module object as the second argument. This allows the addon to completely overwrite exports with a single function instead of adding the function as a property of exports.

要测试它,则运行以下 JavaScript:

¥To test it, run the following JavaScript:

// test.js
const addon = require('./build/Release/addon');

addon((msg) => {
  console.log(msg);
// Prints: 'hello world'
}); 

在这个例子中,回调函数是同步调用的。

¥In this example, the callback function is invoked synchronously.

对象工厂#

¥Object factory

插件可以从 C++ 函数中创建和返回新对象,如下例所示。创建并返回带有属性 msg 的对象,该属性与传给 createObject() 的字符串相呼应:

¥Addons can create and return new objects from within a C++ function as illustrated in the following example. An object is created and returned with a property msg that echoes the string passed to createObject():

// addon.cc
#include <node.h>

namespace demo {

using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  Local<Object> obj = Object::New(isolate);
  obj->Set(context,
           String::NewFromUtf8(isolate,
                               "msg").ToLocalChecked(),
                               args[0]->ToString(context).ToLocalChecked())
           .FromJust();

  args.GetReturnValue().Set(obj);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateObject);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo 

在 JavaScript 中测试它:

¥To test it in JavaScript:

// test.js
const addon = require('./build/Release/addon');

const obj1 = addon('hello');
const obj2 = addon('world');
console.log(obj1.msg, obj2.msg);
// Prints: 'hello world' 

函数工厂#

¥Function factory

另一个常见的场景是创建封装 C++ 函数并将它们返回给 JavaScript 的 JavaScript 函数:

¥Another common scenario is creating JavaScript functions that wrap C++ functions and returning those back to JavaScript:

// addon.cc
#include <node.h>

namespace demo {

using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void MyFunction(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(
      isolate, "hello world").ToLocalChecked());
}

void CreateFunction(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  Local<Context> context = isolate->GetCurrentContext();
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
  Local<Function> fn = tpl->GetFunction(context).ToLocalChecked();

  // omit this to make it anonymous
  fn->SetName(String::NewFromUtf8(
      isolate, "theFunction").ToLocalChecked());

  args.GetReturnValue().Set(fn);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateFunction);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo 

去测试:

¥To test:

// test.js
const addon = require('./build/Release/addon');

const fn = addon();
console.log(fn());
// Prints: 'hello world' 

封装 C++ 对象#

¥Wrapping C++ objects

还可以以允许使用 JavaScript new 运算符创建新实例的方式封装 C++ 对象/类:

¥It is also possible to wrap C++ objects/classes in a way that allows new instances to be created using the JavaScript new operator:

// addon.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::Local;
using v8::Object;

void InitAll(Local<Object> exports) {
  MyObject::Init(exports);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)

}  // namespace demo 

然后,在 myobject.h 中,封装类继承自 node::ObjectWrap

¥Then, in myobject.h, the wrapper class inherits from node::ObjectWrap:

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Local<v8::Object> exports);

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);

  double value_;
};

}  // namespace demo

#endif 

myobject.cc 中,实现要暴露的各种方法。下面,方法 plusOne() 通过将其添加到构造函数的原型中来暴露:

¥In myobject.cc, implement the various methods that are to be exposed. Below, the method plusOne() is exposed by adding it to the constructor's prototype:

// myobject.cc
#include "myobject.h"

namespace demo {

using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::String;
using v8::Value;

MyObject::MyObject(double value) : value_(value) {
}

MyObject::~MyObject() {
}

void MyObject::Init(Local<Object> exports) {
  Isolate* isolate = exports->GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  Local<ObjectTemplate> addon_data_tpl = ObjectTemplate::New(isolate);
  addon_data_tpl->SetInternalFieldCount(1);  // 1 field for the MyObject::New()
  Local<Object> addon_data =
      addon_data_tpl->NewInstance(context).ToLocalChecked();

  // Prepare constructor template
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New, addon_data);
  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  // Prototype
  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

  Local<Function> constructor = tpl->GetFunction(context).ToLocalChecked();
  addon_data->SetInternalField(0, constructor);
  exports->Set(context, String::NewFromUtf8(
      isolate, "MyObject").ToLocalChecked(),
      constructor).FromJust();
}

void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  if (args.IsConstructCall()) {
    // Invoked as constructor: `new MyObject(...)`
    double value = args[0]->IsUndefined() ?
        0 : args[0]->NumberValue(context).FromMaybe(0);
    MyObject* obj = new MyObject(value);
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    // Invoked as plain function `MyObject(...)`, turn into construct call.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons =
        args.Data().As<Object>()->GetInternalField(0)
            .As<Value>().As<Function>();
    Local<Object> result =
        cons->NewInstance(context, argc, argv).ToLocalChecked();
    args.GetReturnValue().Set(result);
  }
}

void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  obj->value_ += 1;

  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
}

}  // namespace demo 

要构建此示例,必须将 myobject.cc 文件添加到 binding.gyp

¥To build this example, the myobject.cc file must be added to the binding.gyp:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [
        "addon.cc",
        "myobject.cc"
      ]
    }
  ]
} 

测试它:

¥Test it with:

// test.js
const addon = require('./build/Release/addon');

const obj = new addon.MyObject(10);
console.log(obj.plusOne());
// Prints: 11
console.log(obj.plusOne());
// Prints: 12
console.log(obj.plusOne());
// Prints: 13 

当对象被垃圾收集时,封装器对象的析构函数将运行。对于析构函数测试,可以使用命令行标志来强制进行垃圾回收。这些标志由底层 V8 JavaScript 引擎提供。它们可能会随时更改或删除。Node.js 或 V8 没有记录它们,并且它们不应该在测试之外使用。

¥The destructor for a wrapper object will run when the object is garbage-collected. For destructor testing, there are command-line flags that can be used to make it possible to force garbage collection. These flags are provided by the underlying V8 JavaScript engine. They are subject to change or removal at any time. They are not documented by Node.js or V8, and they should never be used outside of testing.

在进程或工作线程关闭期间,JS 引擎不会调用析构函数。因此,用户有责任跟踪这些对象并确保正确销毁以避免资源泄漏。

¥During shutdown of the process or worker threads destructors are not called by the JS engine. Therefore it's the responsibility of the user to track these objects and ensure proper destruction to avoid resource leaks.

封装对象工厂#

¥Factory of wrapped objects

另外,可以使用工厂模式来避免使用 JavaScript new 运算符显式创建对象实例:

¥Alternatively, it is possible to use a factory pattern to avoid explicitly creating object instances using the JavaScript new operator:

const obj = addon.createObject();
// instead of:
// const obj = new addon.Object(); 

首先,createObject() 方法在 addon.cc 中实现:

¥First, the createObject() method is implemented in addon.cc:

// addon.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  MyObject::NewInstance(args);
}

void InitAll(Local<Object> exports, Local<Object> module) {
  MyObject::Init(exports->GetIsolate());

  NODE_SET_METHOD(module, "exports", CreateObject);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)

}  // namespace demo 

myobject.h 中,添加了静态方法 NewInstance() 来处理对象的实例化。这个方法代替了 JavaScript 中的 new

¥In myobject.h, the static method NewInstance() is added to handle instantiating the object. This method takes the place of using new in JavaScript:

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Isolate* isolate);
  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  static v8::Global<v8::Function> constructor;
  double value_;
};

}  // namespace demo

#endif 

myobject.cc 中的实现类似于前面的例子:

¥The implementation in myobject.cc is similar to the previous example:

// myobject.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using node::AddEnvironmentCleanupHook;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

// Warning! This is not thread-safe, this addon cannot be used for worker
// threads.
Global<Function> MyObject::constructor;

MyObject::MyObject(double value) : value_(value) {
}

MyObject::~MyObject() {
}

void MyObject::Init(Isolate* isolate) {
  // Prepare constructor template
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  // Prototype
  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

  Local<Context> context = isolate->GetCurrentContext();
  constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked());

  AddEnvironmentCleanupHook(isolate, [](void*) {
    constructor.Reset();
  }, nullptr);
}

void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  if (args.IsConstructCall()) {
    // Invoked as constructor: `new MyObject(...)`
    double value = args[0]->IsUndefined() ?
        0 : args[0]->NumberValue(context).FromMaybe(0);
    MyObject* obj = new MyObject(value);
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    // Invoked as plain function `MyObject(...)`, turn into construct call.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    Local<Object> instance =
        cons->NewInstance(context, argc, argv).ToLocalChecked();
    args.GetReturnValue().Set(instance);
  }
}

void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  const unsigned argc = 1;
  Local<Value> argv[argc] = { args[0] };
  Local<Function> cons = Local<Function>::New(isolate, constructor);
  Local<Context> context = isolate->GetCurrentContext();
  Local<Object> instance =
      cons->NewInstance(context, argc, argv).ToLocalChecked();

  args.GetReturnValue().Set(instance);
}

void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  obj->value_ += 1;

  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
}

}  // namespace demo 

再一次,要构建此示例,必须将 myobject.cc 文件添加到 binding.gyp

¥Once again, to build this example, the myobject.cc file must be added to the binding.gyp:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [
        "addon.cc",
        "myobject.cc"
      ]
    }
  ]
} 

测试它:

¥Test it with:

// test.js
const createObject = require('./build/Release/addon');

const obj = createObject(10);
console.log(obj.plusOne());
// Prints: 11
console.log(obj.plusOne());
// Prints: 12
console.log(obj.plusOne());
// Prints: 13

const obj2 = createObject(20);
console.log(obj2.plusOne());
// Prints: 21
console.log(obj2.plusOne());
// Prints: 22
console.log(obj2.plusOne());
// Prints: 23 

传递封装的对象#

¥Passing wrapped objects around

除了封装和返回 C++ 对象之外,还可以通过使用 Node.js 辅助函数 node::ObjectWrap::Unwrap 将它们解包来传递被封装的对象。以下示例显示了函数 add(),它可以将两个 MyObject 对象作为输入参数:

¥In addition to wrapping and returning C++ objects, it is possible to pass wrapped objects around by unwrapping them with the Node.js helper function node::ObjectWrap::Unwrap. The following examples shows a function add() that can take two MyObject objects as input arguments:

// addon.cc
#include <node.h>
#include <node_object_wrap.h>
#include "myobject.h"

namespace demo {

using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  MyObject::NewInstance(args);
}

void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
      args[0]->ToObject(context).ToLocalChecked());
  MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
      args[1]->ToObject(context).ToLocalChecked());

  double sum = obj1->value() + obj2->value();
  args.GetReturnValue().Set(Number::New(isolate, sum));
}

void InitAll(Local<Object> exports) {
  MyObject::Init(exports->GetIsolate());

  NODE_SET_METHOD(exports, "createObject", CreateObject);
  NODE_SET_METHOD(exports, "add", Add);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)

}  // namespace demo 

myobject.h 中,添加了新的公共方法,以允许在解封装对象后访问私有值。

¥In myobject.h, a new public method is added to allow access to private values after unwrapping the object.

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Isolate* isolate);
  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
  inline double value() const { return value_; }

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static v8::Global<v8::Function> constructor;
  double value_;
};

}  // namespace demo

#endif 

myobject.cc 的实现与之前类似:

¥The implementation of myobject.cc is similar to before:

// myobject.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using node::AddEnvironmentCleanupHook;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

// Warning! This is not thread-safe, this addon cannot be used for worker
// threads.
Global<Function> MyObject::constructor;

MyObject::MyObject(double value) : value_(value) {
}

MyObject::~MyObject() {
}

void MyObject::Init(Isolate* isolate) {
  // Prepare constructor template
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  Local<Context> context = isolate->GetCurrentContext();
  constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked());

  AddEnvironmentCleanupHook(isolate, [](void*) {
    constructor.Reset();
  }, nullptr);
}

void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  if (args.IsConstructCall()) {
    // Invoked as constructor: `new MyObject(...)`
    double value = args[0]->IsUndefined() ?
        0 : args[0]->NumberValue(context).FromMaybe(0);
    MyObject* obj = new MyObject(value);
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    // Invoked as plain function `MyObject(...)`, turn into construct call.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    Local<Object> instance =
        cons->NewInstance(context, argc, argv).ToLocalChecked();
    args.GetReturnValue().Set(instance);
  }
}

void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  const unsigned argc = 1;
  Local<Value> argv[argc] = { args[0] };
  Local<Function> cons = Local<Function>::New(isolate, constructor);
  Local<Context> context = isolate->GetCurrentContext();
  Local<Object> instance =
      cons->NewInstance(context, argc, argv).ToLocalChecked();

  args.GetReturnValue().Set(instance);
}

}  // namespace demo 

测试它:

¥Test it with:

// test.js
const addon = require('./build/Release/addon');

const obj1 = addon.createObject(10);
const obj2 = addon.createObject(20);
const result = addon.add(obj1, obj2);

console.log(result);
// Prints: 30