一、node-gyp

1、node-gyp 是一种构建工具,用于将 c++ 打包成 .node 后缀的 NodeJS 模块,经过 node-gyp 的构建就可以在 NodeJS 环境下使用 c++ 写的模块
2、node-gyp 依赖 python2.7(不能用3.xx版本)、make(UNIX系统下构建工具程序)/msbuild(window下构建工具)、gcc(C++编译工具包)

  1. // mac
  2. brew install python2.7
  3. brew install -y build-essential
  4. // win
  5. npm i -g --production windows-build-tools
  6. // 安装 node-gyp
  7. npm i -g node-gyp

3、node-gyp 常用命令
a、configure
为当前模块生成构建配置文件,win下是 .msvc,unix 下是 .makefile
b、build
构建模块,wind下是调用 msbuild,unix 下是 make
c、rebuild
一次性执行 configure 和 build
d、install
为指定版本的 Node.js 安装开发环境依赖文件,我们电脑里用户目录下的 .node-gyp 就是 install 的结果
e、remove/ clean
移除指定版本的 Node.js 安装开发环境依赖文件 / 移除生成的构建文件。

所以一般是先 node-gyp install 安装开发环境依赖(一些头文件);然后编写 .cpp;然后 node-gyp rebuild

3、binding.gyp 是 node-gyp 的配置文件,就像 webpack.config.js 是 webpack 的配置文件一样。其本身是一个json文件,定义了各种编译相关的内容:

  1. {
  2. "targets": [{
  3. "target_name": "map", // 编译后文件名为 map.node
  4. "sources": [
  5. "map.cc" // 编译入口文件为 map.cc
  6. ]
  7. }]
  8. }

4、一个简单的 demo

  1. // binding.gyp
  2. {
  3. "targets": [{
  4. "target_name": "first",
  5. "sources": [
  6. "first.cpp"
  7. ]
  8. }]
  9. }
  10. // first.cpp
  11. #include <node.h>
  12. namespace __first__
  13. {
  14. using v8::FunctionCallbackInfo;
  15. using v8::Isolate;
  16. using v8::Local;
  17. using v8::Object;
  18. using v8::String;
  19. using v8::Value;
  20. void Method(const FunctionCallbackInfo<Value> &args)
  21. {
  22. Isolate *isolate = args.GetIsolate();
  23. args.GetReturnValue().Set(String::NewFromUtf8(isolate, "first build"));
  24. }
  25. void init(Local<Object> exports)
  26. {
  27. NODE_SET_METHOD(exports, "first", Method);
  28. }
  29. NODE_MODULE(addon, init)
  30. } // namespace __first__

执行 node-gyp rebuild 后就会生成 build 文件夹,里面包含了构建配置文件和最终构建产物 first.node:
image.png
5、binding.gyp 常用配置语法
配置文件十分灵活,甚至可以调用子进程进行运算
a、变量
分为预定义变量(全大写、下划线,如OS)、用户变量(在 variables 字段中声明)、自动变量
b、指令
指令以<! 或者 <!@ 开头,如:

  1. 'variables':[
  2. 'foo':'<! (echo BUild Date <!(date))',
  3. ]

c、条件分支 conditions/target_conditions
d、数组过滤器(排除!、匹配/)
6、binding.gyp 常用配置字段
a、预编译 defines ,用于添加预编译宏

  1. {
  2. "targets": [{
  3. "target_name": "first",
  4. "defines":[
  5. "BAR=some_value"
  6. ]
  7. }]
  8. }

b、头文件搜索路径 include_dirs

  1. {
  2. "targets": [{
  3. "target_name": "first",
  4. "include_dirs":[
  5. '..',
  6. 'include'
  7. ]
  8. }]
  9. }

c、依赖库 libraries

  1. "conditions":[
  2. ["OS==\"mac\"",{}],
  3. ["OS==\"linux\"",{
  4. "libraries":["../lib/xxx.a"]
  5. }]
  6. ]

d、编译类型 type

  1. "type": "shared_library" # 一般动态链接库
  2. "type": "static_library" # 静态链接库
  3. "type": "loadable_module" # C++扩展动态链接库,默认值

e、依赖 dependencies
如果你的C++扩展使用了第三方C++代码,就需要在binding.gyp中将其编译为静态链接库,使其可以被主 target 所依赖

  1. "targets":[
  2. {
  3. "target_name":"a",
  4. "type":"static_library",
  5. "sources":["./deps/xxx/x.c"]
  6. },
  7. {
  8. "target_name":"a",
  9. "dependencies":["a"],
  10. "sources":["./src/xx.cc"]
  11. }
  12. ]

f、复制 copies

  1. "copies":[
  2. {
  3. "destination":"<!(module_root_dir)/build/Release/",
  4. "files":["<!(module_root_dir)/src/third_party/lib/windows/xxx.dll"]
  5. }
  6. ]

g、常用变量
module_root_dir 模块根目录
node_root_dir Node.js 一些文件的根目录
node_gyp_dir node-gyp 这个包的根目录
node_lib_file 用于编译时的 Node.js 库文件

二、编写简单 Addon

  • 固定套路

1、模块注册入口 NODE_MODULE(addon, init)
2、init 函数内挂载需要导出的内容 NODE_SET_METHOD(exports, “first”, Method);
3、实现 Method,通过 args.GetReturnValue().Set 设置返回值,注意 Set 参数必须是一个句柄,而不是一个值

  1. #include <node.h> // 引入头文件,包含了一些类型定义、宏定义以及其它头文件
  2. namespace __first__
  3. {
  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. {
  12. Isolate *isolate = args.GetIsolate();
  13. args.GetReturnValue().Set(String::NewFromUtf8(isolate, "first build"));
  14. }
  15. void init(Local<Object> exports)
  16. {
  17. NODE_SET_METHOD(exports, "first", Method); // 在 exports 对象上挂载 first 方法,方法体为 Method
  18. }
  19. NODE_MODULE(addon, init) // Native 模块注册入口
  20. } // namespace __first__
  • 可以接受参数的函数 ```cpp

    include

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;

// 实现 “add” 函数 void Add(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate();

  1. // 判断参数个数是否合法
  2. if (args.Length() < 2)
  3. {
  4. // 不合法则跑错
  5. isolate->ThrowException(Exception::TypeError(
  6. String::NewFromUtf8(isolate, "Wrong number of arguments")));
  7. return;
  8. }
  9. // 判断参数类型是否合法
  10. if (!args[0]->IsNumber() || !args[1]->IsNumber())
  11. {
  12. isolate->ThrowException(Exception::TypeError(
  13. String::NewFromUtf8(isolate, "Wrong arguments")));
  14. return;
  15. }
  16. // 计算第一个参数加第二个参数的 `double` 值
  17. // 并新生成一个 Local<Number> 句柄,将计算出来的 `double` 传入
  18. double value = args[0]->NumberValue() + args[1]->NumberValue();
  19. Local<Number> num = Number::New(isolate, value);
  20. // 设置返回值为新生成的 `num`
  21. args.GetReturnValue().Set(num);

}

void Init(Local exports) { NODE_SET_METHOD(exports, “add”, Add); }

NODE_MODULE(addon, Init)

} // namespace demo

  1. C++ 里判断参数合法性是比较麻烦的,这一步可以在JS层做,即确保 JS 在调用 C++ 模块导出的方法时参数都是正确的可以减少 C++ 代码的复杂性
  2. - 回调函数,即可以传JS函数作为参数
  3. ```cpp
  4. #include <node.h>
  5. namespace demo
  6. {
  7. using v8::Function;
  8. using v8::FunctionCallbackInfo;
  9. using v8::Isolate;
  10. using v8::Local;
  11. using v8::Null;
  12. using v8::Object;
  13. using v8::String;
  14. using v8::Value;
  15. void RunCallback(const FunctionCallbackInfo<Value> &args)
  16. {
  17. Isolate *isolate = args.GetIsolate();
  18. Local<Function> cb = Local<Function>::Cast(args[0]); // 类型转换,将args[0]转为函数句柄
  19. const unsigned argc = 1;
  20. Local<Value> argv[argc] = {String::NewFromUtf8(isolate, "hello world")};
  21. cb->Call(Null(isolate), argc, argv); // 调用 cb,并传参,依次为 this,参数个数,参数值
  22. }
  23. void Init(Local<Object> exports, Local<Object> module)
  24. {
  25. NODE_SET_METHOD(module, "exports", RunCallback); // 这种导出等于 module.exports = RunCallback,而不是在 exports 上挂载
  26. }
  27. NODE_MODULE(addon, Init)
  28. } // namespace demo
  • 返回对象 ```cpp

    include

namespace demo {

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

void CreateObject(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate();

  1. Local<Object> obj = Object::New(isolate);
  2. obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());
  3. args.GetReturnValue().Set(obj);

}

void Init(Local exports, Local module) { NODE_SET_METHOD(module, “exports”, CreateObject); }

NODE_MODULE(addon, Init)

} // namespace demo

  1. - 返回函数
  2. ```cpp
  3. #include <node.h>
  4. namespace demo
  5. {
  6. using v8::Function;
  7. using v8::FunctionCallbackInfo;
  8. using v8::FunctionTemplate;
  9. using v8::Isolate;
  10. using v8::Local;
  11. using v8::Object;
  12. using v8::String;
  13. using v8::Value;
  14. void MyFunction(const FunctionCallbackInfo<Value> &args)
  15. {
  16. Isolate *isolate = args.GetIsolate();
  17. args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));
  18. }
  19. void CreateFunction(const FunctionCallbackInfo<Value> &args)
  20. {
  21. Isolate *isolate = args.GetIsolate();
  22. Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
  23. Local<Function> fn = tpl->GetFunction();
  24. // omit this to make it anonymous
  25. fn->SetName(String::NewFromUtf8(isolate, "theFunction"));
  26. args.GetReturnValue().Set(fn);
  27. }
  28. void Init(Local<Object> exports, Local<Object> module)
  29. {
  30. NODE_SET_METHOD(module, "exports", CreateFunction);
  31. }
  32. NODE_MODULE(addon, Init)
  33. } // namespace demo

本节编写 addon 的方式还是比较原始,目前流行的方式是使用 Nan

三、使用 NAN 编写 Addon

NAN 全称 Native Abstract For Node.js,表现上是一个 npm 包 nan,安装后可以得到一堆C++头文件,里面是一堆宏定义,用于屏蔽 Node.js 和 v8 版本差异导致的 API 差异,使得开发者不用关心这些琐碎细节,做到一次编写,到处编译(各种不同 Node.js 版本)

  • NAN 固定套路

1、NODE_MODULE(addon,Init) 模块注册入口
2、NAN_MODULE_INIT(Init) 初始化
3、Nan::SetMethod(target,’echo’,Echo) target 上挂载 echo,这里 target 就是 exports
4、NAN_METHOD 实现函数 Echo
这个套路里多了几个宏定义:NAN_MOTHOD,NAN_MODULE_INIT 展开后和第二节套路差不多

  • 宏 NAN_MODULE_INIT 用于模块初始化,其内 target 就是 exports
  • 模块函数导出 Nan::SetMethod
  • 调用回调函数 Nan::MakeCallback() ```cpp

    include

namespace map {

using Nan::FunctionCallbackInfo; using v8::Array; using v8::Function; using v8::Int32; using v8::Local; using v8::Object; using v8::Value;

NAN_METHOD(Map) { Local array = info[0].As(); // info 就是原来的 args Local func = info[1].As();

  1. Local<Array> ret = Nan::New<Array>(array->Length());
  2. Local<Value> null = Nan::Null();
  3. Local<Value> a[3] = {Nan::New<Object>(), null, array};
  4. for (uint32_t i = 0; i < array->Length(); i++)
  5. {
  6. a[0] = array->Get(i);
  7. a[1] = Nan::New<Int32>(i);
  8. Local<Value> v = Nan::MakeCallback(info.This(), func, 3, a);
  9. ret->Set(i, v);
  10. }
  11. info.GetReturnValue().Set(ret);

}

NAN_MODULE_INIT(Init) { Nan::SetMethod(target, “map”, Map); }

NODE_MODULE(map, Init)

} // namespace map

  1. - NAN 常用 API
  2. 1、函数参数类型 Nan::FunctionCallbackInfo,不再使用 v8::FunctionCallbackInfo<br />2、函数返回类型 Nan::ReturnValue,不再使用 v8::ReturnValue<br />3、函数声明 NAN_METHOD(函数名)<br />4、函数模版 Nan::New<FunctionTemplate>
  3. ```cpp
  4. NAN_METHOD(ECHO){}
  5. void Init(Local<Object> target){
  6. Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(ECHO);
  7. Local<Function> fn = tpl->GetFunction();
  8. }

5、往对象上挂载函数 Nan::SetMethod
和 Nan::Export 效果差不多,但更推荐使用 Nan::SetMethod

  1. Nan::SetMethod(target,'echo',Echo)

6、函数原型链设置 Nan::SetPrototypeTemplate

  1. NAN_METHOD(ECHO){}
  2. void Init(Local<Object> target){
  3. Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>();
  4. Nan::SetPrototypeTemplate(tpl,'echo',ECHO)
  5. }

7、句柄作用域 Nan::HandleScope Nan::EscapableHandleScope
8、持久句柄 Nan::Persistent
9、创建元数据 Nan::New()

  1. Local<Number> number = Nan::New(233);
  2. Local<Boolean> boolean = Nan::New(true);
  3. Local<String> str = Nan::New('string').ToLocalChecked(); // 创建string时返回的时待实句柄
  4. Nan::Undefined()
  5. Nan::Null()
  6. Nan::True()
  7. Nan::False()
  8. Nan::EmptyString()

10、类型转换 Nan::To ,返回的是待实句柄

  1. v8::Local<v8::Value> value;
  2. Nan::MaybeLocal<v8::String> str = Nan::To<v8::String>(value)

11、对象/数组 设置 Nan::Set Nan::Get
12、JSON 操作 Nan::JSON 在其实例上有 Parse 和 Stringify 两个方法,而不是静态函数

四、将 C++ 类制作成 Addon

分五个文件 myobject.h ; myobject.cpp ; entry.cpp ; binding.gyp ; package.json

  • myobject.h 文件

1、声明一个C++类继承自 Nan::ObjectWrap

  1. class MyObject : public Nan::ObjectWrap{
  2. private:
  3. explicit MyObject(double value=0); // C++构造函数
  4. ~MyObject(); // C++ 构析函数
  5. double value_; // 实例属性
  6. }

2、声明一个静态方法作为 JS 类的构造函数

  1. class MyObject : public Nan::ObjectWrap{
  2. private:
  3. explicit MyObject(double value=0); // C++构造函数
  4. ~MyObject(); // C++ 构析函数
  5. double value_; // 实例属性
  6. static NAN_METHOD(JSConstructor) // 用于 JS 测的 new JSConstructor() 调用
  7. }

3、声明一个静态方法作为模块初始化函数

  1. class MyObject : public Nan::ObjectWrap{
  2. public:
  3. static NAN_MODULE_INIT(Init) // 模块初始化函数
  4. private:
  5. explicit MyObject(double value=0); // C++构造函数
  6. ~MyObject(); // C++ 构析函数
  7. double value_; // 实例属性
  8. static NAN_METHOD(JSConstructor) // 用于 JS 测的 new JSConstructor() 调用
  9. }

4、声明一个静态方法作为 JS 端原型链函数

  1. class MyObject : public Nan::ObjectWrap{
  2. public:
  3. static NAN_MODULE_INIT(Init) // 模块初始化函数
  4. private:
  5. explicit MyObject(double value=0); // C++构造函数
  6. ~MyObject(); // C++ 构析函数
  7. double value_; // 实例属性
  8. static NAN_METHOD(JSConstructor) // 用于 JS 测的 new JSConstructor() 调用
  9. static NAN_METHOD(JSPrototypeMethod) // 用于 JS 测的 a.JSPrototypeMethod() 调用
  10. }

5、最终 my-object.h 文件如下:

  1. #include <nan.h>
  2. namespace __myobject__
  3. {
  4. using v8::Function;
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Object;
  9. using v8::Value;
  10. class MyObject : public Nan::ObjectWrap
  11. {
  12. public:
  13. static NAN_MODULE_INIT(Init); // 模块初始化函数
  14. private:
  15. explicit MyObject(double value = 0);
  16. ~MyObject();
  17. static NAN_METHOD(JSConstructor); // 用于 JS 测的 new JSConstructor() 调用
  18. static NAN_METHOD(JSPrototypeMethod); // 用于 JS 测的 a.JSPrototypeMethod() 调用
  19. double value_;
  20. };
  21. } // namespace __myobject__
  • entry.cpp 初始化 ```cpp

    include

    include “myobject.h”

namespace myobject { using v8::Local; using v8::Number; using v8::Object;

NAN_MODULE_INIT(Init) { MyObject::Init(target); }

NODE_MODULE(myobject, Init)

} // namespace myobject

  1. - myobject.cpp 实现
  2. 1、实现 Init 方法
  3. ```cpp
  4. NAN_MODULE_INIT(MyObject::Init)
  5. {
  6. Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(JSConstructor);
  7. tpl->SetClassName(Nan::New("MyObject").ToLocalChecked());
  8. tpl->InstanceTemplate()->SetInternalFieldCount(1); // C++层面内置字段,JS无感知
  9. Nan::SetPrototypeMethod(tpl, "JSPrototypeMethod", JSPrototypeMethod); // 原型方法
  10. Nan::Set(target, Nan::New("JSConstructor").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked());
  11. }

2、实现 JSConstuctor 方法

  1. NAN_METHOD(MyObject::JSConstructor)
  2. {
  3. Local<Number> value = Local<Number>::Cast(info[0]);
  4. MyObject *obj = new MyObject(value->Value());
  5. obj->Wrap(info.This()); // info.This() 就是JS端 new 调用时的 this,这一步将 C++ 类实例和 JS 的 this 进行了绑定
  6. info.GetReturnValue().Set(info.This()); // 返回 this,不过并不能通过 this.value_ 获取值,因为是C++通过内置字段挂载过来的
  7. }

3、实现原型函数 JSPrototypeMethod

  1. NAN_METHOD(MyObject::JSPrototypeMethod)
  2. {
  3. Isolate *isolate = info.GetIsolate();
  4. MyObject *obj = ObjectWrap::Unwrap<MyObject>(info.Holder()); // Holder 是调用时的 this,这里通过 Unwrap 取得了于this绑定的C++类实例
  5. obj->value_ += 1;
  6. info.GetReturnValue().Set(Number::New(isolate, obj->value_));
  7. }

4、完整的 myobject.cc

  1. #include "nan.h"
  2. #include "myobject.h"
  3. namespace __myobject__
  4. {
  5. using v8::Context;
  6. using v8::FunctionTemplate;
  7. using v8::Isolate;
  8. using v8::Local;
  9. using v8::Number;
  10. using v8::String;
  11. MyObject::MyObject(double value) : value_(value)
  12. {
  13. }
  14. MyObject::~MyObject()
  15. {
  16. }
  17. NAN_MODULE_INIT(MyObject::Init)
  18. {
  19. Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(JSConstructor);
  20. tpl->SetClassName(Nan::New("MyObject").ToLocalChecked());
  21. tpl->InstanceTemplate()->SetInternalFieldCount(1); // C++层面内置字段,JS无感知
  22. Nan::SetPrototypeMethod(tpl, "JSPrototypeMethod", JSPrototypeMethod); // 原型方法
  23. Nan::Set(target, Nan::New("JSConstructor").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked());
  24. }
  25. NAN_METHOD(MyObject::JSConstructor)
  26. {
  27. Local<Number> value = Local<Number>::Cast(info[0]);
  28. MyObject *obj = new MyObject(value->Value());
  29. obj->Wrap(info.This()); // info.This() 就是JS端 new 调用时的 this,这一步将 C++ 类实例和 JS 的 this 进行了绑定
  30. info.GetReturnValue().Set(info.This()); // 返回 this,不过并不能通过 this.value_ 获取值,因为是C++通过内置字段挂载过来的
  31. }
  32. NAN_METHOD(MyObject::JSPrototypeMethod)
  33. {
  34. Isolate *isolate = info.GetIsolate();
  35. MyObject *obj = ObjectWrap::Unwrap<MyObject>(info.Holder()); // Holder 是调用时的 this,这里通过 Unwrap 取得了于this绑定的C++类实例
  36. obj->value_ += 1;
  37. info.GetReturnValue().Set(Number::New(isolate, obj->value_));
  38. }
  39. } // namespace __myobject__
  • binding.gyp

    1. {
    2. "targets": [{
    3. "target_name": "myobject",
    4. "sources": ["entry.cc", "myobject.cc"],
    5. "include_dirs": [
    6. "<!(node -e \"require('nan')\")"
    7. ],
    8. }]
    9. }
  • package.json

    1. {
    2. "name": "myobject",
    3. "version": "1.0.0",
    4. "description": "Demo for MyObject.",
    5. "homepage": "https://github.com/XadillaX/nyaa-nodejs-demo#readme",
    6. "dependencies": {
    7. "nan": "^2.14.2"
    8. }
    9. }

    最后执行 node-gyp rebuild 就可以生成 build/Release/myobject.node,也即最终的 Addon

  • JS 测使用

    1. const addon = require('./build/Release/myobject.node');
    2. const obj = new addon.JSConstructor(10);
    3. const plus = obj.JSPrototypeMethod();
    4. console.log(obj,obj.__proto__,plus);

    最后贴以下几个常见宏的扩展: ```cpp // NODE_MODULE

    define NAN_MODULE_WORKER_ENABLED(module_name, registration) \

    NODE_MODULE(module_name, registration)

define NAN_MODULE_WORKER_ENABLED(module_name, registration) \

  1. extern "C" NODE_MODULE_EXPORT void \
  2. NAN_CONCAT(node_register_module_v, NODE_MODULE_VERSION)( \
  3. v8::Local<v8::Object> exports, v8::Local<v8::Value> module, \
  4. v8::Local<v8::Context> context) \
  5. { \
  6. registration(exports); // 这里可以看到传人了 exports \
  7. }

// NAN_MODULE_INIT

define NAN_MODULE_INIT(name) \

  1. void name(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target)

if NODE_MODULE_VERSION < IOJS_3_0_MODULE_VERSION

typedef v8::Handle ADDON_REGISTER_FUNCTION_ARGS_TYPE;

else

typedef v8::Local ADDON_REGISTER_FUNCTION_ARGS_TYPE;

endif

// NAN_METHOD

define NAN_METHOD(name) \

  1. Nan::NAN_METHOD_RETURN_TYPE name(Nan::NAN_METHOD_ARGS_TYPE info)

typedef void NAN_METHOD_RETURN_TYPE; typedef const FunctionCallbackInfo& NAN_METHOD_ARGS_TYPE;

  1. 一点心得:<br />1v8 基础类型很重要,没有 Nan 的替换版本
  2. ```cpp
  3. v8::Value v8::String v8::Number v8::Boolean v8::Function v8::FunctionCallbackInfo
  4. v8::Local v8::HandleScope v8::Isolate v8::Context
  5. // Nan 新增类型
  6. Nan::Callback

2、操作类的尽量用 Nan::xxx 来做

  1. v8::Local<v8::String> s = Nan::New("test");
  2. Nan::Utf8String string(s);
  3. Nan::Callback* listener = new Nan::Callback(info[1].As<v8::Function>());

五、一些帮助文档

  1. https://github.com/nodejs/nan
  2. 《Node.js 来一打C++扩展》死月 著
  3. https://v8docs.nodesource.com/node-14.15/index.html
  4. https://v8.dev/
  5. https://www.w3schools.com/cpp/