C++ 插件| Node.js API 文档

打印 上一主题 下一主题

主题 504|帖子 504|积分 1512

目录
C++ 插件#

中英对照
插件是用 C++ 编写的动态链接共享对象。 require() 函数可以将插件加载为普通的 Node.js 模块。 插件提供了 JavaScript 和 C/C++ 库之间的接口。
实现插件有三种选择:Node-API、nan 或直接使用内部 V8、libuv 和 Node.js 库。 除非需要直接访问 Node-API 未暴露的功能,否则请使用 Node-API。 有关 Node-API 的更多信息,请参阅使用 Node-API 的 C/C++ 插件
不使用 Node-API 时,实现插件很复杂,涉及若干组件和 API 的知识:

  • V8: Node.js 用来提供 JavaScript 实现的 C++ 库。 V8 提供了创建对象、调用函数等的机制。 V8 的 API 主要记录在 v8.h 头文件(Node.js 源代码树中的 deps/v8/include/v8.h)中,该文件也可在线获取。
  • libuv: 实现 Node.js 事件循环、其工作线程和平台所有异步行为的 C 库。 它还可以作为跨平台的抽象库,提供跨所有主要操作系统对许多常见系统任务的简单的、类似于 POSIX 的访问,例如与文件系统、套接字、定时器和系统事件的交互。 libuv 还提供类似于 POSIX 线程的线程抽象,用于需要超越标准事件循环的更复杂的异步插件。 插件作者应该避免使用 I/O 或其他时间密集型任务阻塞事件循环,通过将工作通过 libuv 分流到非阻塞系统操作、工作线程、或 libuv 线程的自定义使用来实现。
  • 内部 Node.js 库。 Node.js 自身导出了插件可以使用的 C++ API,其中最重要的是 node::ObjectWrap 类。
  • Node.js 包括了其他静态链接库,包括 OpenSSL。 这些其他库位于 Node.js 源代码树的 deps/ 目录中。 只有 libuv、OpenSSL、V8 和 zlib 符号被 Node.js 有目的地重新导出,并且可以被插件在不同程度上使用。 有关其他信息,请参阅链接到 Node.js 中包含的库
以下所有示例均可下载,并可用作插件的起点。
你好世界#

中英对照
这个 "Hello world" 示例是一个简单的插件,用 C++ 编写,相当于以下 JavaScript 代码:
  1. module.exports.hello = () => 'world';
复制代码
首先,创建文件 hello.cc:
  1. // hello.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::FunctionCallbackInfo;
  5. using v8::Isolate;
  6. using v8::Local;
  7. using v8::Object;
  8. using v8::String;
  9. using v8::Value;
  10. void Method(const FunctionCallbackInfo<Value>& args) {
  11.   Isolate* isolate = args.GetIsolate();
  12.   args.GetReturnValue().Set(String::NewFromUtf8(
  13.       isolate, "world").ToLocalChecked());
  14. }
  15. void Initialize(Local<Object> exports) {
  16.   NODE_SET_METHOD(exports, "hello", Method);
  17. }
  18. NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
  19. }  // namespace demo
复制代码
所有 Node.js 插件都必须按照以下模式导出初始化函数:
  1. void Initialize(Local<Object> exports);
  2. NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
复制代码
NODE_MODULE 后面没有分号,因为它不是函数(参见 node.h)。
module_name 必须与最终二进制文件的文件名匹配(不包括 .node 后缀)。
在 hello.cc 示例中,初始化函数为 Initialize,插件模块名称为 addon。
使用 node-gyp 构建插件时,使用宏 NODE_GYP_MODULE_NAME 作为 NODE_MODULE() 的第一个参数将确保最终二进制文件的名称将传给 NODE_MODULE()。
上下文感知的插件#

中英对照
在某些环境中,可能需要在多个上下文中多次加载 Node.js 插件。 例如,Electron 运行时在单个进程中运行多个 Node.js 实例。 每个实例都有自己的 require() 缓存,因此当通过 require() 加载时,每个实例都需要原生插件才能正确运行。 这意味着插件必须支持多个初始化。
可以使用宏 NODE_MODULE_INITIALIZER 构建上下文感知插件,该宏扩展为 Node.js 在加载插件时期望找到的函数的名称。 因此可以像下面的示例一样初始化插件:
  1. using namespace v8;
  2. extern "C" NODE_MODULE_EXPORT void
  3. NODE_MODULE_INITIALIZER(Local<Object> exports,
  4.                         Local<Value> module,
  5.                         Local<Context> context) {
  6.   /* 在此处执行插件初始化步骤。 */
  7. }
复制代码
另一种选择是使用宏 NODE_MODULE_INIT(),它也将构建上下文感知插件。 与 NODE_MODULE() 不同,NODE_MODULE() 用于围绕给定的 addon 初始化函数构造插件,而 NODE_MODULE_INIT() 用作此类初始化器的声明,然后是函数体。
在调用 NODE_MODULE_INIT() 之后,可以在函数体内使用以下三个变量:

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

  • 定义一个类,该类将保存每个插件实例数据并具有该形式的静态成员
    1. static void DeleteInstance(void* data) {
    2.   // 将 `data` 转换为类的实例并将其删除。
    3. }
    复制代码
  • 在插件初始值设定项中堆分配此类的实例。 这可以使用 new 关键字来完成。
  • 调用 node::AddEnvironmentCleanupHook(),将上面创建的实例和指向 DeleteInstance() 的指针传给它。 这将确保在拆除环境时删除实例。
  • 将类的实例存储在 v8::External 中,并且
  • 通过将 v8::External 传给创建原生支持的 ​​JavaScript 函数的 v8::FunctionTemplate::New() 或 v8::Function::New(),将 v8::External 传给所有暴露给 JavaScript 的方法。 v8::FunctionTemplate::New() 或 v8::Function::New() 的第三个参数接受 v8::External 并使用 v8::FunctionCallbackInfo::Data() 方法使其在原生回调中可用。
这将确保每个插件实例数据到达可以从 JavaScript 调用的每个绑定。 每个插件实例数据还必须传入到插件可能创建的任何异步回调中。
以下示例说明了上下文感知插件的实现:
  1. #include <node.h>
  2. using namespace v8;
  3. class AddonData {
  4. public:
  5.   explicit AddonData(Isolate* isolate):
  6.       call_count(0) {
  7.     // 确保在环境清理时删除此每个插件实例的数据。
  8.     node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
  9.   }
  10.   // 每个插件的数据。
  11.   int call_count;
  12.   static void DeleteInstance(void* data) {
  13.     delete static_cast<AddonData*>(data);
  14.   }
  15. };
  16. static void Method(const v8::FunctionCallbackInfo<v8::Value>& info) {
  17.   // 检索每个插件实例的数据。
  18.   AddonData* data =
  19.       reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());
  20.   data->call_count++;
  21.   info.GetReturnValue().Set((double)data->call_count);
  22. }
  23. // 将此插件初始化为上下文感知。
  24. NODE_MODULE_INIT(/* exports, module, context */) {
  25.   Isolate* isolate = context->GetIsolate();
  26.   // 为该插件实例创建新的 `AddonData` 实例,
  27.   // 并将其生命周期与 Node.js 环境的生命周期联系起来。
  28.   AddonData* data = new AddonData(isolate);
  29.   // 将数据包装在 `v8::External` 中,
  30.   // 以便可以将其传给暴露的方法。
  31.   Local<External> external = External::New(isolate, data);
  32.   // 将方法 `Method` 暴露给 JavaScript,
  33.   // 并通过将 `external` 作为第三个参数传给 `FunctionTemplate` 构造函数
  34.   // 来确保它接收到上面创建的每个插件实例的数据。
  35.   exports->Set(context,
  36.                String::NewFromUtf8(isolate, "method").ToLocalChecked(),
  37.                FunctionTemplate::New(isolate, Method, external)
  38.                   ->GetFunction(context).ToLocalChecked()).FromJust();
  39. }
复制代码
工作线程的支持#

中英对照
版本历史为了从多个 Node.js 环境(例如主线程和工作线程)加载,插件需要:

  • 成为 Node-API 插件,或
  • 如上所述使用 NODE_MODULE_INIT() 声明为上下文感知
为了支持 Worker 线程,插件需要清理它们可能在此类线程存在时分配的任何资源。 这可以通过使用 AddEnvironmentCleanupHook() 函数来实现:
  1. void AddEnvironmentCleanupHook(v8::Isolate* isolate,
  2.                                void (*fun)(void* arg),
  3.                                void* arg);
复制代码
此函数添加了一个钩子,该钩子将在给定的 Node.js 实例关闭之前运行。 如有必要,可以在使用具有相同签名的 RemoveEnvironmentCleanupHook() 运行这些钩子之前将其删除。 回调按后进先出的顺序运行。
如有必要,还有一对额外的 AddEnvironmentCleanupHook() 和 RemoveEnvironmentCleanupHook() 重载,其中清理钩子采用回调函数。 这可用于关闭异步资源,例如插件注册的任何 libuv 句柄。
以下 addon.cc 使用 AddEnvironmentCleanupHook:
  1. // addon.cc
  2. #include <node.h>
  3. #include <assert.h>
  4. #include <stdlib.h>
  5. using node::AddEnvironmentCleanupHook;
  6. using v8::HandleScope;
  7. using v8::Isolate;
  8. using v8::Local;
  9. using v8::Object;
  10. // 注意:在实际应用程序中,不要依赖静态/全局数据。
  11. static char cookie[] = "yum yum";
  12. static int cleanup_cb1_called = 0;
  13. static int cleanup_cb2_called = 0;
  14. static void cleanup_cb1(void* arg) {
  15.   Isolate* isolate = static_cast<Isolate*>(arg);
  16.   HandleScope scope(isolate);
  17.   Local<Object> obj = Object::New(isolate);
  18.   assert(!obj.IsEmpty());  // 断言 VM 仍旧存活
  19.   assert(obj->IsObject());
  20.   cleanup_cb1_called++;
  21. }
  22. static void cleanup_cb2(void* arg) {
  23.   assert(arg == static_cast<void*>(cookie));
  24.   cleanup_cb2_called++;
  25. }
  26. static void sanity_check(void*) {
  27.   assert(cleanup_cb1_called == 1);
  28.   assert(cleanup_cb2_called == 1);
  29. }
  30. // 将此插件初始化为上下文感知。
  31. NODE_MODULE_INIT(/* exports, module, context */) {
  32.   Isolate* isolate = context->GetIsolate();
  33.   AddEnvironmentCleanupHook(isolate, sanity_check, nullptr);
  34.   AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie);
  35.   AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate);
  36. }
复制代码
通过运行在 JavaScript 中进行测试:
  1. // test.js
  2. require('./build/Release/addon');
复制代码
构建#

中英对照
编写源代码后,必须将其编译为二进制 addon.node 文件。 为此,请在项目的顶层创建名为 binding.gyp 的文件,使用类似 JSON 的格式描述模块的构建配置。 该文件由 node-gyp 使用,这是一个专门为编译 Node.js 插件而编写的工具。
  1. {
  2.   "targets": [
  3.     {
  4.       "target_name": "addon",
  5.       "sources": [ "hello.cc" ]
  6.     }
  7.   ]
  8. }
复制代码
node-gyp 实用工具的一个版本作为 npm 的一部分与 Node.js 捆绑和分发。 此版本不直接提供给开发人员使用,仅旨在支持使用 npm install 命令编译和安装插件的能力。 希望直接使用 node-gyp 的开发人员可以使用命令 npm install -g node-gyp 安装它。 有关更多信息,包括特定于平台的要求,请参阅 node-gyp 安装说明
创建 binding.gyp 文件后,使用 node-gyp configure 为当前平台生成适当的项目构建文件。 这将在 build/ 目录中生成 Makefile(在 Unix 平台上)或 vcxproj 文件(在 Windows 上)。
接下来,调用 node-gyp build 命令生成编译后的 addon.node 文件。 这将被放入 build/Release/ 目录。
当使用 npm install 安装 Node.js 插件时,npm 使用它自己的 node-gyp 捆绑版本来执行相同的一组操作,按需为用户平台生成插件的编译版本。
构建完成后,可以通过将 require() 指向构建的 addon.node 模块在 Node.js 中使用二进制插件:
  1. // hello.js
  2. const addon = require('./build/Release/addon');
  3. console.log(addon.hello());
  4. // 打印: 'world'
复制代码
因为编译的插件二进制文件的确切路径可能会因编译方式而异(即有时它可能在 ./build/Debug/ 中),插件可以使用绑定包来加载已编译的模块。
虽然 bindings 包实现在如何定位插件模块方面更为复杂,但它本质上使用了类似于以下内容的 try…catch 模式:
  1. try {
  2.   return require('./build/Release/addon.node');
  3. } catch (err) {
  4.   return require('./build/Debug/addon.node');
  5. }
复制代码
链接到 Node.js 自带的库#

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

  • 当 node-gyp 运行时,它会检测 Node.js 的特定发布版本并下载完整的源代码压缩包或仅下载头文件。 如果下载了完整的源代码,插件将可以完全访问完整的 Node.js 依赖项集。 但是,如果只下载 Node.js 头文件,则只有 Node.js 导出的符号可用。
  • node-gyp 可以使用指向本地 Node.js 源镜像的 --nodedir 标志运行。 使用此选项,插件将可以访问完整的依赖项集。
使用 require() 加载插件#

中英对照
已编译的插件二进制文件的文件扩展名是 .node(与 .dll 或 .so 相反)。 require() 函数用于查找具有 .node 文件扩展名的文件并将它们初始化为动态链接库。
调用 require() 时,通常可以省略 .node 扩展名,Node.js 仍会找到并初始化插件。 但是,有一个注意事项,Node.js 将首先尝试定位和加载碰巧共享相同基本名称的模块或 JavaScript 文件。 例如,如果在与二进制 addon.node 相同的目录中有一个文件 addon.js,那么 require('addon') 将优先于 addon.js 文件并加载它。
Node.js 的原生抽象#

中英对照
本文档中说明的每个示例都直接使用 Node.js 和 V8 API 来实现插件。 从一个 V8 版本到下一个版本(以及一个主要的 Node.js 版本到下一个版本),V8 API 可能并且已经发生了巨大的变化。 每次更改时,插件可能需要更新和重新编译才能继续运行。 Node.js 发布计划旨在最小化此类更改的频率和影响,但 Node.js 几乎无法确保 V8 API 的稳定性。
Node.js 的原生抽象(或 nan)提供了一组工具,建议插件开发人员使用这些工具来保持 V8 和 Node.js 过去和未来版本之间的兼容性。 有关如何使用它的说明,请参见 nan 示例
Node-API#

中英对照
 
稳定性: 2 - 稳定 
Node-API 是用于构建原生插件的 API。 它独立于底层 JavaScript 运行时(例如 V8),并作为 Node.js 自身的一部分进行维护。 此 API 将在 Node.js 的各个版本中保持稳定的应用程序二进制接口 (ABI)。 它旨在将插件与底层 JavaScript 引擎中的更改隔离开来,并允许为一个版本编译的模块无需重新编译即可在更高版本的 Node.js 上运行。 插件是使用本文档中概述的相同方法/工具(node-gyp 等)构建/打包的。唯一的区别是原生代码使用的 API 集。 使用 Node-API 中可用的函数,而不是使用 V8 或 Node.js 原生抽象的 API。
创建和维护受益于 Node-API 提供的 ABI 稳定性的插件会带来某些实现的注意事项
要在上面的 "Hello world" 示例中使用 Node-API,则将 hello.cc 的内容替换为以下内容。 所有其他指令保持不变。
  1. // 使用 Node-API 的 hello.cc
  2. #include <node_api.h>
  3. namespace demo {
  4. napi_value Method(napi_env env, napi_callback_info args) {
  5.   napi_value greeting;
  6.   napi_status status;
  7.   status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting);
  8.   if (status != napi_ok) return nullptr;
  9.   return greeting;
  10. }
  11. napi_value init(napi_env env, napi_value exports) {
  12.   napi_status status;
  13.   napi_value fn;
  14.   status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
  15.   if (status != napi_ok) return nullptr;
  16.   status = napi_set_named_property(env, exports, "hello", fn);
  17.   if (status != napi_ok) return nullptr;
  18.   return exports;
  19. }
  20. NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
  21. }  // namespace demo
复制代码
可用的函数以及如何使用它们记录在使用 Node-API 的 C/C++ 插件中。
插件示例#

中英对照
以下是一些旨在帮助开发人员入门的示例插件。 这些示例使用 V8 API。 请参阅在线 V8 手册以获取有关各种 V8 调用的帮助,以及 V8 的嵌入器指南以获取对所使用的几个概念(例如句柄、作用域、函数模板等)的解释。
这些示例中的每一个都使用以下 binding.gyp 文件:
  1. {
  2.   "targets": [
  3.     {
  4.       "target_name": "addon",
  5.       "sources": [ "addon.cc" ]
  6.     }
  7.   ]
  8. }
复制代码
如果有多个 .cc 文件,只需将附加文件名添加到 sources 数组:
  1. "sources": ["addon.cc", "myexample.cc"]
复制代码
一旦 binding.gyp 文件准备就绪,就可以使用 node-gyp 配置和构建示例插件:
  1. $ node-gyp configure build
复制代码
函数的参数#

中英对照
插件通常会暴露可以从 Node.js 中运行的 JavaScript 访问的对象和函数。 当从 JavaScript 调用函数时,输入参数和返回值必须映射到 C/C++ 代码和从 C/C++ 代码映射。
以下示例说明了如何读取从 JavaScript 传入的函数参数以及如何返回结果:
  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Exception;
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Number;
  9. using v8::Object;
  10. using v8::String;
  11. using v8::Value;
  12. // 这是 "add" 方法的实现
  13. // 输入参数使用
  14. // const FunctionCallbackInfo<Value>& args 结构传入
  15. void Add(const FunctionCallbackInfo<Value>& args) {
  16.   Isolate* isolate = args.GetIsolate();
  17.   // 检查传入的参数数量。
  18.   if (args.Length() < 2) {
  19.     // 抛出传回 JavaScript 的错误
  20.     isolate->ThrowException(Exception::TypeError(
  21.         String::NewFromUtf8(isolate,
  22.                             "Wrong number of arguments").ToLocalChecked()));
  23.     return;
  24.   }
  25.   // 检查参数类型
  26.   if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
  27.     isolate->ThrowException(Exception::TypeError(
  28.         String::NewFromUtf8(isolate,
  29.                             "Wrong arguments").ToLocalChecked()));
  30.     return;
  31.   }
  32.   // 执行操作
  33.   double value =
  34.       args[0].As<Number>()->Value() + args[1].As<Number>()->Value();
  35.   Local<Number> num = Number::New(isolate, value);
  36.   // 设置返回值
  37.   // (使用传入的 FunctionCallbackInfo<Value>&)
  38.   args.GetReturnValue().Set(num);
  39. }
  40. void Init(Local<Object> exports) {
  41.   NODE_SET_METHOD(exports, "add", Add);
  42. }
  43. NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
  44. }  // namespace demo
复制代码
编译后,可以在 Node.js 中加载和使用示例插件:
  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. console.log('This should be eight:', addon.add(3, 5));
复制代码
回调#

中英对照
插件中的常见做法是将 JavaScript 函数传给 C++ 函数并从那里执行它们。 以下示例说明了如何调用此类回调:
  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Context;
  5. using v8::Function;
  6. using v8::FunctionCallbackInfo;
  7. using v8::Isolate;
  8. using v8::Local;
  9. using v8::Null;
  10. using v8::Object;
  11. using v8::String;
  12. using v8::Value;
  13. void RunCallback(const FunctionCallbackInfo<Value>& args) {
  14.   Isolate* isolate = args.GetIsolate();
  15.   Local<Context> context = isolate->GetCurrentContext();
  16.   Local<Function> cb = Local<Function>::Cast(args[0]);
  17.   const unsigned argc = 1;
  18.   Local<Value> argv[argc] = {
  19.       String::NewFromUtf8(isolate,
  20.                           "hello world").ToLocalChecked() };
  21.   cb->Call(context, Null(isolate), argc, argv).ToLocalChecked();
  22. }
  23. void Init(Local<Object> exports, Local<Object> module) {
  24.   NODE_SET_METHOD(module, "exports", RunCallback);
  25. }
  26. NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
  27. }  // namespace demo
复制代码
此示例使用 Init() 的双参数形式,它接收完整的 module 对象作为第二个参数。 这允许插件使用单个函数完全覆盖 exports,而不是将该函数添加为 exports 的属性。
要测试它,则运行以下 JavaScript:
  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. addon((msg) => {
  4.   console.log(msg);
  5. // 打印: 'hello world'
  6. });
复制代码
在这个例子中,回调函数是同步调用的。
对象工厂#

中英对照
插件可以从 C++ 函数中创建和返回新对象,如下例所示。 创建并返回带有属性 msg 的对象,该属性与传给 createObject() 的字符串相呼应:
  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Context;
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Object;
  9. using v8::String;
  10. using v8::Value;
  11. void CreateObject(const FunctionCallbackInfo<Value>& args) {
  12.   Isolate* isolate = args.GetIsolate();
  13.   Local<Context> context = isolate->GetCurrentContext();
  14.   Local<Object> obj = Object::New(isolate);
  15.   obj->Set(context,
  16.            String::NewFromUtf8(isolate,
  17.                                "msg").ToLocalChecked(),
  18.                                args[0]->ToString(context).ToLocalChecked())
  19.            .FromJust();
  20.   args.GetReturnValue().Set(obj);
  21. }
  22. void Init(Local<Object> exports, Local<Object> module) {
  23.   NODE_SET_METHOD(module, "exports", CreateObject);
  24. }
  25. NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
  26. }  // namespace demo
复制代码
在 JavaScript 中测试它:
  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. const obj1 = addon('hello');
  4. const obj2 = addon('world');
  5. console.log(obj1.msg, obj2.msg);
  6. // 打印: 'hello world'
复制代码
函数工厂#

中英对照
另一个常见的场景是创建封装 C++ 函数并将它们返回给 JavaScript 的 JavaScript 函数:
  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Context;
  5. using v8::Function;
  6. using v8::FunctionCallbackInfo;
  7. using v8::FunctionTemplate;
  8. using v8::Isolate;
  9. using v8::Local;
  10. using v8::Object;
  11. using v8::String;
  12. using v8::Value;
  13. void MyFunction(const FunctionCallbackInfo<Value>& args) {
  14.   Isolate* isolate = args.GetIsolate();
  15.   args.GetReturnValue().Set(String::NewFromUtf8(
  16.       isolate, "hello world").ToLocalChecked());
  17. }
  18. void CreateFunction(const FunctionCallbackInfo<Value>& args) {
  19.   Isolate* isolate = args.GetIsolate();
  20.   Local<Context> context = isolate->GetCurrentContext();
  21.   Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
  22.   Local<Function> fn = tpl->GetFunction(context).ToLocalChecked();
  23.   // 省略它以使其匿名
  24.   fn->SetName(String::NewFromUtf8(
  25.       isolate, "theFunction").ToLocalChecked());
  26.   args.GetReturnValue().Set(fn);
  27. }
  28. void Init(Local<Object> exports, Local<Object> module) {
  29.   NODE_SET_METHOD(module, "exports", CreateFunction);
  30. }
  31. NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
  32. }  // namespace demo
复制代码
去测试:
  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. const fn = addon();
  4. console.log(fn());
  5. // 打印: 'hello world'
复制代码
封装 C++ 对象#

中英对照
还可以以允许使用 JavaScript new 运算符创建新实例的方式封装 C++ 对象/类:
  1. // addon.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using v8::Local;
  6. using v8::Object;
  7. void InitAll(Local<Object> exports) {
  8.   MyObject::Init(exports);
  9. }
  10. NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
  11. }  // namespace demo
复制代码
然后,在 myobject.h 中,封装类继承自 node::ObjectWrap:
  1. // myobject.h
  2. #ifndef MYOBJECT_H
  3. #define MYOBJECT_H
  4. #include <node.h>
  5. #include <node_object_wrap.h>
  6. namespace demo {
  7. class MyObject : public node::ObjectWrap {
  8. public:
  9.   static void Init(v8::Local<v8::Object> exports);
  10. private:
  11.   explicit MyObject(double value = 0);
  12.   ~MyObject();
  13.   static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  14.   static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  15.   double value_;
  16. };
  17. }  // namespace demo
  18. #endif
复制代码
在 myobject.cc 中,实现要暴露的各种方法。 下面,方法 plusOne() 通过将其添加到构造函数的原型中来暴露:
  1. // myobject.cc
  2. #include "myobject.h"
  3. namespace demo {
  4. using v8::Context;
  5. using v8::Function;
  6. using v8::FunctionCallbackInfo;
  7. using v8::FunctionTemplate;
  8. using v8::Isolate;
  9. using v8::Local;
  10. using v8::Number;
  11. using v8::Object;
  12. using v8::ObjectTemplate;
  13. using v8::String;
  14. using v8::Value;
  15. MyObject::MyObject(double value) : value_(value) {
  16. }
  17. MyObject::~MyObject() {
  18. }
  19. void MyObject::Init(Local<Object> exports) {
  20.   Isolate* isolate = exports->GetIsolate();
  21.   Local<Context> context = isolate->GetCurrentContext();
  22.   Local<ObjectTemplate> addon_data_tpl = ObjectTemplate::New(isolate);
  23.   addon_data_tpl->SetInternalFieldCount(1);  // MyObject::New() 的 1 个字段
  24.   Local<Object> addon_data =
  25.       addon_data_tpl->NewInstance(context).ToLocalChecked();
  26.   // 准备构造函数模板
  27.   Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New, addon_data);
  28.   tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked());
  29.   tpl->InstanceTemplate()->SetInternalFieldCount(1);
  30.   // 原型
  31.   NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
  32.   Local<Function> constructor = tpl->GetFunction(context).ToLocalChecked();
  33.   addon_data->SetInternalField(0, constructor);
  34.   exports->Set(context, String::NewFromUtf8(
  35.       isolate, "MyObject").ToLocalChecked(),
  36.       constructor).FromJust();
  37. }
  38. void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  39.   Isolate* isolate = args.GetIsolate();
  40.   Local<Context> context = isolate->GetCurrentContext();
  41.   if (args.IsConstructCall()) {
  42.     // 作为构造函数调用:`new MyObject(...)`
  43.     double value = args[0]->IsUndefined() ?
  44.         0 : args[0]->NumberValue(context).FromMaybe(0);
  45.     MyObject* obj = new MyObject(value);
  46.     obj->Wrap(args.This());
  47.     args.GetReturnValue().Set(args.This());
  48.   } else {
  49.     // 作为普通函数 `MyObject(...)` 调用,变成构造调用。
  50.     const int argc = 1;
  51.     Local<Value> argv[argc] = { args[0] };
  52.     Local<Function> cons =
  53.         args.Data().As<Object>()->GetInternalField(0).As<Function>();
  54.     Local<Object> result =
  55.         cons->NewInstance(context, argc, argv).ToLocalChecked();
  56.     args.GetReturnValue().Set(result);
  57.   }
  58. }
  59. void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  60.   Isolate* isolate = args.GetIsolate();
  61.   MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  62.   obj->value_ += 1;
  63.   args.GetReturnValue().Set(Number::New(isolate, obj->value_));
  64. }
  65. }  // namespace demo
复制代码
要构建此示例,必须将 myobject.cc 文件添加到 binding.gyp:
  1. {
  2.   "targets": [
  3.     {
  4.       "target_name": "addon",
  5.       "sources": [
  6.         "addon.cc",
  7.         "myobject.cc"
  8.       ]
  9.     }
  10.   ]
  11. }
复制代码
测试它:
  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. const obj = new addon.MyObject(10);
  4. console.log(obj.plusOne());
  5. // 打印: 11
  6. console.log(obj.plusOne());
  7. // 打印: 12
  8. console.log(obj.plusOne());
  9. // 打印: 13
复制代码
当对象被垃圾收集时,封装器对象的析构函数将运行。 对于析构函数测试,可以使用命令行标志来强制进行垃圾回收。 这些标志由底层 V8 JavaScript 引擎提供。 它们可能会随时更改或删除。 Node.js 或 V8 没有记录它们,并且它们不应该在测试之外使用。
在进程或工作线程关闭期间,JS 引擎不会调用析构函数。 因此,用户有责任跟踪这些对象并确保正确销毁以避免资源泄漏。
封装对象的工厂#

中英对照
另外,可以使用工厂模式来避免使用 JavaScript new 运算符显式创建对象实例:
  1. const obj = addon.createObject();
  2. // 而不是:
  3. // const obj = new addon.Object();
复制代码
首先,createObject() 方法在 addon.cc 中实现:
  1. // addon.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Object;
  9. using v8::String;
  10. using v8::Value;
  11. void CreateObject(const FunctionCallbackInfo<Value>& args) {
  12.   MyObject::NewInstance(args);
  13. }
  14. void InitAll(Local<Object> exports, Local<Object> module) {
  15.   MyObject::Init(exports->GetIsolate());
  16.   NODE_SET_METHOD(module, "exports", CreateObject);
  17. }
  18. NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
  19. }  // namespace demo
复制代码
在 myobject.h 中,添加了静态方法 NewInstance() 来处理对象的实例化。 这个方法代替了 JavaScript 中的 new :
  1. // myobject.h
  2. #ifndef MYOBJECT_H
  3. #define MYOBJECT_H
  4. #include <node.h>
  5. #include <node_object_wrap.h>
  6. namespace demo {
  7. class MyObject : public node::ObjectWrap {
  8. public:
  9.   static void Init(v8::Isolate* isolate);
  10.   static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
  11. private:
  12.   explicit MyObject(double value = 0);
  13.   ~MyObject();
  14.   static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  15.   static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  16.   static v8::Global<v8::Function> constructor;
  17.   double value_;
  18. };
  19. }  // namespace demo
  20. #endif
复制代码
myobject.cc 中的实现类似于前面的例子:
  1. // myobject.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using node::AddEnvironmentCleanupHook;
  6. using v8::Context;
  7. using v8::Function;
  8. using v8::FunctionCallbackInfo;
  9. using v8::FunctionTemplate;
  10. using v8::Global;
  11. using v8::Isolate;
  12. using v8::Local;
  13. using v8::Number;
  14. using v8::Object;
  15. using v8::String;
  16. using v8::Value;
  17. // 警告!这不是线程安全的,
  18. // 这个插件不能用于工作线程。
  19. Global<Function> MyObject::constructor;
  20. MyObject::MyObject(double value) : value_(value) {
  21. }
  22. MyObject::~MyObject() {
  23. }
  24. void MyObject::Init(Isolate* isolate) {
  25.   // 准备构造函数模板
  26.   Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  27.   tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked());
  28.   tpl->InstanceTemplate()->SetInternalFieldCount(1);
  29.   // 原型
  30.   NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
  31.   Local<Context> context = isolate->GetCurrentContext();
  32.   constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked());
  33.   AddEnvironmentCleanupHook(isolate, [](void*) {
  34.     constructor.Reset();
  35.   }, nullptr);
  36. }
  37. void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  38.   Isolate* isolate = args.GetIsolate();
  39.   Local<Context> context = isolate->GetCurrentContext();
  40.   if (args.IsConstructCall()) {
  41.     // 作为构造函数调用:`new MyObject(...)`
  42.     double value = args[0]->IsUndefined() ?
  43.         0 : args[0]->NumberValue(context).FromMaybe(0);
  44.     MyObject* obj = new MyObject(value);
  45.     obj->Wrap(args.This());
  46.     args.GetReturnValue().Set(args.This());
  47.   } else {
  48.     // 作为普通函数 `MyObject(...)` 调用,变成构造调用。
  49.     const int argc = 1;
  50.     Local<Value> argv[argc] = { args[0] };
  51.     Local<Function> cons = Local<Function>::New(isolate, constructor);
  52.     Local<Object> instance =
  53.         cons->NewInstance(context, argc, argv).ToLocalChecked();
  54.     args.GetReturnValue().Set(instance);
  55.   }
  56. }
  57. void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  58.   Isolate* isolate = args.GetIsolate();
  59.   const unsigned argc = 1;
  60.   Local<Value> argv[argc] = { args[0] };
  61.   Local<Function> cons = Local<Function>::New(isolate, constructor);
  62.   Local<Context> context = isolate->GetCurrentContext();
  63.   Local<Object> instance =
  64.       cons->NewInstance(context, argc, argv).ToLocalChecked();
  65.   args.GetReturnValue().Set(instance);
  66. }
  67. void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  68.   Isolate* isolate = args.GetIsolate();
  69.   MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  70.   obj->value_ += 1;
  71.   args.GetReturnValue().Set(Number::New(isolate, obj->value_));
  72. }
  73. }  // namespace demo
复制代码
再一次,要构建此示例,必须将 myobject.cc 文件添加到 binding.gyp:
  1. {
  2.   "targets": [
  3.     {
  4.       "target_name": "addon",
  5.       "sources": [
  6.         "addon.cc",
  7.         "myobject.cc"
  8.       ]
  9.     }
  10.   ]
  11. }
复制代码
测试它:
  1. // test.js
  2. const createObject = require('./build/Release/addon');
  3. const obj = createObject(10);
  4. console.log(obj.plusOne());
  5. // 打印: 11
  6. console.log(obj.plusOne());
  7. // 打印: 12
  8. console.log(obj.plusOne());
  9. // 打印: 13
  10. const obj2 = createObject(20);
  11. console.log(obj2.plusOne());
  12. // 打印: 21
  13. console.log(obj2.plusOne());
  14. // 打印: 22
  15. console.log(obj2.plusOne());
  16. // 打印: 23
复制代码
传递封装的对象#

中英对照
除了封装和返回 C++ 对象之外,还可以通过使用 Node.js 辅助函数 node::ObjectWrap::Unwrap 将它们解包来传递被包装的对象。 以下示例显示了函数 add(),它可以将两个 MyObject 对象作为输入参数:
  1. // addon.cc
  2. #include <node.h>
  3. #include <node_object_wrap.h>
  4. #include "myobject.h"
  5. namespace demo {
  6. using v8::Context;
  7. using v8::FunctionCallbackInfo;
  8. using v8::Isolate;
  9. using v8::Local;
  10. using v8::Number;
  11. using v8::Object;
  12. using v8::String;
  13. using v8::Value;
  14. void CreateObject(const FunctionCallbackInfo<Value>& args) {
  15.   MyObject::NewInstance(args);
  16. }
  17. void Add(const FunctionCallbackInfo<Value>& args) {
  18.   Isolate* isolate = args.GetIsolate();
  19.   Local<Context> context = isolate->GetCurrentContext();
  20.   MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
  21.       args[0]->ToObject(context).ToLocalChecked());
  22.   MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
  23.       args[1]->ToObject(context).ToLocalChecked());
  24.   double sum = obj1->value() + obj2->value();
  25.   args.GetReturnValue().Set(Number::New(isolate, sum));
  26. }
  27. void InitAll(Local<Object> exports) {
  28.   MyObject::Init(exports->GetIsolate());
  29.   NODE_SET_METHOD(exports, "createObject", CreateObject);
  30.   NODE_SET_METHOD(exports, "add", Add);
  31. }
  32. NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
  33. }  // namespace demo
复制代码
在 myobject.h 中,添加了新的公共方法,以允许在解封装对象后访问私有值。
  1. // myobject.h
  2. #ifndef MYOBJECT_H
  3. #define MYOBJECT_H
  4. #include <node.h>
  5. #include <node_object_wrap.h>
  6. namespace demo {
  7. class MyObject : public node::ObjectWrap {
  8. public:
  9.   static void Init(v8::Isolate* isolate);
  10.   static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
  11.   inline double value() const { return value_; }
  12. private:
  13.   explicit MyObject(double value = 0);
  14.   ~MyObject();
  15.   static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  16.   static v8::Global<v8::Function> constructor;
  17.   double value_;
  18. };
  19. }  // namespace demo
  20. #endif
复制代码
myobject.cc 的实现与之前类似:
  1. // myobject.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using node::AddEnvironmentCleanupHook;
  6. using v8::Context;
  7. using v8::Function;
  8. using v8::FunctionCallbackInfo;
  9. using v8::FunctionTemplate;
  10. using v8::Global;
  11. using v8::Isolate;
  12. using v8::Local;
  13. using v8::Object;
  14. using v8::String;
  15. using v8::Value;
  16. // 警告!这不是线程安全的,
  17. // 这个插件不能用于工作线程。
  18. Global<Function> MyObject::constructor;
  19. MyObject::MyObject(double value) : value_(value) {
  20. }
  21. MyObject::~MyObject() {
  22. }
  23. void MyObject::Init(Isolate* isolate) {
  24.   // 准备构造函数模板
  25.   Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  26.   tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked());
  27.   tpl->InstanceTemplate()->SetInternalFieldCount(1);
  28.   Local<Context> context = isolate->GetCurrentContext();
  29.   constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked());
  30.   AddEnvironmentCleanupHook(isolate, [](void*) {
  31.     constructor.Reset();
  32.   }, nullptr);
  33. }
  34. void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  35.   Isolate* isolate = args.GetIsolate();
  36.   Local<Context> context = isolate->GetCurrentContext();
  37.   if (args.IsConstructCall()) {
  38.     // 作为构造函数调用:`new MyObject(...)`
  39.     double value = args[0]->IsUndefined() ?
  40.         0 : args[0]->NumberValue(context).FromMaybe(0);
  41.     MyObject* obj = new MyObject(value);
  42.     obj->Wrap(args.This());
  43.     args.GetReturnValue().Set(args.This());
  44.   } else {
  45.     // 作为普通函数 `MyObject(...)` 调用,变成构造调用。
  46.     const int argc = 1;
  47.     Local<Value> argv[argc] = { args[0] };
  48.     Local<Function> cons = Local<Function>::New(isolate, constructor);
  49.     Local<Object> instance =
  50.         cons->NewInstance(context, argc, argv).ToLocalChecked();
  51.     args.GetReturnValue().Set(instance);
  52.   }
  53. }
  54. void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  55.   Isolate* isolate = args.GetIsolate();
  56.   const unsigned argc = 1;
  57.   Local<Value> argv[argc] = { args[0] };
  58.   Local<Function> cons = Local<Function>::New(isolate, constructor);
  59.   Local<Context> context = isolate->GetCurrentContext();
  60.   Local<Object> instance =
  61.       cons->NewInstance(context, argc, argv).ToLocalChecked();
  62.   args.GetReturnValue().Set(instance);
  63. }
  64. }  // namespace demo
复制代码
测试它:
  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. const obj1 = addon.createObject(10);
  4. const obj2 = addon.createObject(20);
  5. const result = addon.add(obj1, obj2);
  6. console.log(result);
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

写过一篇

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表