一、node-gyp1、node-gyp 是一种构建工具,用于将 c++ 打包成 .node 后缀的 NodeJS 模块,经过 node-gyp 的构建就可以在 NodeJS 环境下使用 c++ 写的模块 2、node-gyp 依赖 python2.7(不能用3.xx版本)、make(UNIX系统下构建工具程序)/msbuild(window下构建工具)、gcc(C++编译工具包)
// mac brew install python2 . 7 brew install - y build - essential // win npm i - g -- production windows - build - tools // 安装 node - gyp 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文件,定义了各种编译相关的内容:
{ "targets" : [{ "target_name" : "map" , // 编译后文件名为 map . node "sources" : [ "map.cc" // 编译入口文件为 map . cc ] }] }
4、一个简单的 demo
// binding . gyp { "targets" : [{ "target_name" : "first" , "sources" : [ "first.cpp" ] }] } // first . cpp # include < node . h > namespace __first__ { 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 , "first build" )); } void init ( Local < Object > exports ) { NODE_SET_METHOD ( exports , "first" , Method ); } NODE_MODULE ( addon , init ) } // namespace __first__
执行 node-gyp rebuild 后就会生成 build 文件夹,里面包含了构建配置文件和最终构建产物 first.node: 5、binding.gyp 常用配置语法 配置文件十分灵活,甚至可以调用子进程进行运算 a、变量 分为预定义变量(全大写、下划线,如OS)、用户变量(在 variables 字段中声明)、自动变量 b、指令 指令以<! 或者 <!@ 开头,如:
'variables' :[ 'foo' : '<! (echo BUild Date <!(date))' , ]
c、条件分支 conditions/target_conditions d、数组过滤器(排除!、匹配/) 6、binding.gyp 常用配置字段 a、预编译 defines ,用于添加预编译宏
{ "targets" : [{ "target_name" : "first" , "defines" :[ "BAR=some_value" ] }] }
b、头文件搜索路径 include_dirs
{ "targets" : [{ "target_name" : "first" , "include_dirs" :[ '..' , 'include' ] }] }
c、依赖库 libraries
"conditions" :[ [ "OS==\"mac\"" ,{}], [ "OS==\"linux\"" ,{ "libraries" :[ "../lib/xxx.a" ] }] ]
d、编译类型 type
"type" : "shared_library" # 一般动态链接库 "type" : "static_library" # 静态链接库 "type" : "loadable_module" # C++扩展动态链接库,默认值
e、依赖 dependencies 如果你的C++扩展使用了第三方C++代码,就需要在binding.gyp中将其编译为静态链接库,使其可以被主 target 所依赖
"targets" :[ { "target_name" : "a" , "type" : "static_library" , "sources" :[ "./deps/xxx/x.c" ] }, { "target_name" : "a" , "dependencies" :[ "a" ], "sources" :[ "./src/xx.cc" ] } ]
f、复制 copies
"copies" :[ { "destination" : "<!(module_root_dir)/build/Release/" , "files" :[ "<!(module_root_dir)/src/third_party/lib/windows/xxx.dll" ] } ]
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 参数必须是一个句柄,而不是一个值
#include <node.h> // 引入头文件,包含了一些类型定义、宏定义以及其它头文件 namespace __first__ { 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 , "first build" )); } void init ( Local < Object > exports ) { NODE_SET_METHOD ( exports , "first" , Method ); // 在 exports 对象上挂载 first 方法,方法体为 Method } NODE_MODULE ( addon , init ) // Native 模块注册入口 } // namespace __first__
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();
// 判断参数个数是否合法 if ( args . Length () < 2 ) { // 不合法则跑错 isolate -> ThrowException ( Exception :: TypeError ( String :: NewFromUtf8 ( isolate , "Wrong number of arguments" ))); return ; } // 判断参数类型是否合法 if (! args [ 0 ]-> IsNumber () || ! args [ 1 ]-> IsNumber ()) { isolate -> ThrowException ( Exception :: TypeError ( String :: NewFromUtf8 ( isolate , "Wrong arguments" ))); return ; } // 计算第一个参数加第二个参数的 `double` 值 // 并新生成一个 Local<Number> 句柄,将计算出来的 `double` 传入 double value = args [ 0 ]-> NumberValue () + args [ 1 ]-> NumberValue (); Local < Number > num = Number :: New ( isolate , value ); // 设置返回值为新生成的 `num` args . GetReturnValue (). Set ( num ); }
void Init(Local exports)
{
NODE_SET_METHOD(exports, “add”, Add);
}
NODE_MODULE(addon, Init)
} // namespace demo
C ++ 里判断参数合法性是比较麻烦的,这一步可以在 JS 层做,即确保 JS 在调用 C ++ 模块导出的方法时参数都是正确的可以减少 C ++ 代码的复杂性 - 回调函数,即可以传 JS 函数作为参数 ```cpp #include <node.h> namespace demo { 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<Function> cb = Local<Function>::Cast(args[0]); // 类型转换,将args[0]转为函数句柄 const unsigned argc = 1; Local<Value> argv[argc] = {String::NewFromUtf8(isolate, "hello world")}; cb->Call(Null(isolate), argc, argv); // 调用 cb,并传参,依次为 this,参数个数,参数值 } void Init(Local<Object> exports, Local<Object> module) { NODE_SET_METHOD(module, "exports", RunCallback); // 这种导出等于 module.exports = RunCallback,而不是在 exports 上挂载 } NODE_MODULE(addon, Init) } // namespace demo
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();
Local < Object > obj = Object :: New ( isolate ); obj -> Set ( String :: NewFromUtf8 ( isolate , "msg" ), args [ 0 ]-> ToString ()); args . GetReturnValue (). Set ( obj ); }
void Init(Local exports, Local module)
{
NODE_SET_METHOD(module, “exports”, CreateObject);
}
NODE_MODULE(addon, Init)
} // namespace demo
- 返回函数 ```cpp #include <node.h> namespace demo { 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")); } void CreateFunction(const FunctionCallbackInfo<Value> &args) { Isolate *isolate = args.GetIsolate(); Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction); Local<Function> fn = tpl->GetFunction(); // omit this to make it anonymous fn->SetName(String::NewFromUtf8(isolate, "theFunction")); args.GetReturnValue().Set(fn); } void Init(Local<Object> exports, Local<Object> module) { NODE_SET_METHOD(module, "exports", CreateFunction); } NODE_MODULE(addon, Init) } // namespace demo 本节编写 addon 的方式还是比较原始,目前流行的方式是使用 Nan
三、使用 NAN 编写 AddonNAN 全称 Native Abstract For Node.js,表现上是一个 npm 包 nan,安装后可以得到一堆C++头文件,里面是一堆宏定义,用于屏蔽 Node.js 和 v8 版本差异导致的 API 差异,使得开发者不用关心这些琐碎细节,做到一次编写,到处编译(各种不同 Node.js 版本)
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();
Local < Array > ret = Nan :: New < Array >( array -> Length ()); Local < Value > null = Nan :: Null (); Local < Value > a [ 3 ] = { Nan :: New < Object >(), null , array }; for ( uint32_t i = 0 ; i < array -> Length (); i ++) { a [ 0 ] = array -> Get ( i ); a [ 1 ] = Nan :: New < Int32 >( i ); Local < Value > v = Nan :: MakeCallback ( info . This (), func , 3 , a ); ret -> Set ( i , v ); } info . GetReturnValue (). Set ( ret ); }
NAN_MODULE_INIT(Init)
{
Nan::SetMethod(target, “map”, Map);
}
NODE_MODULE(map, Init)
} // namespace map
- NAN 常用 API 1 、函数参数类型 Nan :: FunctionCallbackInfo ,不再使用 v8 :: FunctionCallbackInfo < br /> 2 、函数返回类型 Nan :: ReturnValue ,不再使用 v8 :: ReturnValue < br /> 3 、函数声明 NAN_METHOD (函数名)< br /> 4 、函数模版 Nan :: New < FunctionTemplate > ```cpp NAN_METHOD(ECHO){} void Init(Local<Object> target){ Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(ECHO); Local<Function> fn = tpl->GetFunction(); } 5、往对象上挂载函数 Nan::SetMethod 和 Nan::Export 效果差不多,但更推荐使用 Nan::SetMethod
Nan :: SetMethod ( target , 'echo' , Echo )
6、函数原型链设置 Nan::SetPrototypeTemplate
NAN_METHOD ( ECHO ){} void Init ( Local < Object > target ){ Local < FunctionTemplate > tpl = Nan :: New < FunctionTemplate >(); Nan :: SetPrototypeTemplate ( tpl , 'echo' , ECHO ) }
7、句柄作用域 Nan::HandleScope Nan::EscapableHandleScope 8、持久句柄 Nan::Persistent 9、创建元数据 Nan::New()
Local < Number > number = Nan :: New ( 233 ); Local < Boolean > boolean = Nan :: New ( true ); Local < String > str = Nan :: New ( 'string' ). ToLocalChecked (); // 创建string时返回的时待实句柄 Nan :: Undefined () Nan :: Null () Nan :: True () Nan :: False () Nan :: EmptyString ()
10、类型转换 Nan::To ,返回的是待实句柄
v8 :: Local < v8 :: Value > value ; 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
1、声明一个C++类继承自 Nan::ObjectWrap
class MyObject : public Nan :: ObjectWrap { private : explicit MyObject ( double value = 0 ); // C++构造函数 ~ MyObject (); // C++ 构析函数 double value_ ; // 实例属性 }
2、声明一个静态方法作为 JS 类的构造函数
class MyObject : public Nan :: ObjectWrap { private : explicit MyObject ( double value = 0 ); // C++构造函数 ~ MyObject (); // C++ 构析函数 double value_ ; // 实例属性 static NAN_METHOD ( JSConstructor ) // 用于 JS 测的 new JSConstructor() 调用 }
3、声明一个静态方法作为模块初始化函数
class MyObject : public Nan :: ObjectWrap { public : static NAN_MODULE_INIT ( Init ) // 模块初始化函数 private : explicit MyObject ( double value = 0 ); // C++构造函数 ~ MyObject (); // C++ 构析函数 double value_ ; // 实例属性 static NAN_METHOD ( JSConstructor ) // 用于 JS 测的 new JSConstructor() 调用 }
4、声明一个静态方法作为 JS 端原型链函数
class MyObject : public Nan :: ObjectWrap { public : static NAN_MODULE_INIT ( Init ) // 模块初始化函数 private : explicit MyObject ( double value = 0 ); // C++构造函数 ~ MyObject (); // C++ 构析函数 double value_ ; // 实例属性 static NAN_METHOD ( JSConstructor ) // 用于 JS 测的 new JSConstructor() 调用 static NAN_METHOD ( JSPrototypeMethod ) // 用于 JS 测的 a.JSPrototypeMethod() 调用 }
5、最终 my-object.h 文件如下:
#include <nan.h> namespace __myobject__ { using v8 :: Function ; using v8 :: FunctionCallbackInfo ; using v8 :: Isolate ; using v8 :: Local ; using v8 :: Object ; using v8 :: Value ; class MyObject : public Nan :: ObjectWrap { public : static NAN_MODULE_INIT ( Init ); // 模块初始化函数 private : explicit MyObject ( double value = 0 ); ~ MyObject (); static NAN_METHOD ( JSConstructor ); // 用于 JS 测的 new JSConstructor() 调用 static NAN_METHOD ( JSPrototypeMethod ); // 用于 JS 测的 a.JSPrototypeMethod() 调用 double value_ ; }; } // 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
- myobject . cpp 实现 1 、实现 Init 方法 ```cpp NAN_MODULE_INIT(MyObject::Init) { Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(JSConstructor); tpl->SetClassName(Nan::New("MyObject").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); // C++层面内置字段,JS无感知 Nan::SetPrototypeMethod(tpl, "JSPrototypeMethod", JSPrototypeMethod); // 原型方法 Nan::Set(target, Nan::New("JSConstructor").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); } 2、实现 JSConstuctor 方法
NAN_METHOD ( MyObject :: JSConstructor ) { Local < Number > value = Local < Number >:: Cast ( info [ 0 ]); MyObject * obj = new MyObject ( value -> Value ()); obj -> Wrap ( info . This ()); // info.This() 就是JS端 new 调用时的 this,这一步将 C++ 类实例和 JS 的 this 进行了绑定 info . GetReturnValue (). Set ( info . This ()); // 返回 this,不过并不能通过 this.value_ 获取值,因为是C++通过内置字段挂载过来的 }
3、实现原型函数 JSPrototypeMethod
NAN_METHOD ( MyObject :: JSPrototypeMethod ) { Isolate * isolate = info . GetIsolate (); MyObject * obj = ObjectWrap :: Unwrap < MyObject >( info . Holder ()); // Holder 是调用时的 this,这里通过 Unwrap 取得了于this绑定的C++类实例 obj -> value_ += 1 ; info . GetReturnValue (). Set ( Number :: New ( isolate , obj -> value_ )); }
4、完整的 myobject.cc
#include "nan.h" #include "myobject.h" namespace __myobject__ { using v8 :: Context ; using v8 :: FunctionTemplate ; using v8 :: Isolate ; using v8 :: Local ; using v8 :: Number ; using v8 :: String ; MyObject :: MyObject ( double value ) : value_ ( value ) { } MyObject ::~ MyObject () { } NAN_MODULE_INIT ( MyObject :: Init ) { Local < FunctionTemplate > tpl = Nan :: New < FunctionTemplate >( JSConstructor ); tpl -> SetClassName ( Nan :: New ( "MyObject" ). ToLocalChecked ()); tpl -> InstanceTemplate ()-> SetInternalFieldCount ( 1 ); // C++层面内置字段,JS无感知 Nan :: SetPrototypeMethod ( tpl , "JSPrototypeMethod" , JSPrototypeMethod ); // 原型方法 Nan :: Set ( target , Nan :: New ( "JSConstructor" ). ToLocalChecked (), Nan :: GetFunction ( tpl ). ToLocalChecked ()); } NAN_METHOD ( MyObject :: JSConstructor ) { Local < Number > value = Local < Number >:: Cast ( info [ 0 ]); MyObject * obj = new MyObject ( value -> Value ()); obj -> Wrap ( info . This ()); // info.This() 就是JS端 new 调用时的 this,这一步将 C++ 类实例和 JS 的 this 进行了绑定 info . GetReturnValue (). Set ( info . This ()); // 返回 this,不过并不能通过 this.value_ 获取值,因为是C++通过内置字段挂载过来的 } NAN_METHOD ( MyObject :: JSPrototypeMethod ) { Isolate * isolate = info . GetIsolate (); MyObject * obj = ObjectWrap :: Unwrap < MyObject >( info . Holder ()); // Holder 是调用时的 this,这里通过 Unwrap 取得了于this绑定的C++类实例 obj -> value_ += 1 ; info . GetReturnValue (). Set ( Number :: New ( isolate , obj -> value_ )); } } // namespace __myobject__
binding.gyp
{ "targets" : [{ "target_name" : "myobject" , "sources" : [ "entry.cc" , "myobject.cc" ], "include_dirs" : [ "<!(node -e \"require('nan')\")" ], }] }
package.json
{ "name" : "myobject" , "version" : "1.0.0" , "description" : "Demo for MyObject." , "homepage" : "https://github.com/XadillaX/nyaa-nodejs-demo#readme" , "dependencies" : { "nan" : "^2.14.2" } }
最后执行 node-gyp rebuild 就可以生成 build/Release/myobject.node,也即最终的 Addon
JS 测使用
const addon = require ( './build/Release/myobject.node' ); const obj = new addon . JSConstructor ( 10 ); const plus = obj . JSPrototypeMethod (); 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) \extern "C" NODE_MODULE_EXPORT void \ NAN_CONCAT ( node_register_module_v , NODE_MODULE_VERSION )( \ v8 :: Local < v8 :: Object > exports , v8 :: Local < v8 :: Value > module , \ v8 :: Local < v8 :: Context > context ) \ { \ registration ( exports ); // 这里可以看到传人了 exports \ } // NAN_MODULE_INIT
define NAN_MODULE_INIT(name) \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) \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;
一点心得:< br /> 1 、 v8 基础类型很重要,没有 Nan 的替换版本 ```cpp v8::Value v8::String v8::Number v8::Boolean v8::Function v8::FunctionCallbackInfo v8::Local v8::HandleScope v8::Isolate v8::Context // Nan 新增类型 Nan::Callback 2、操作类的尽量用 Nan::xxx 来做
v8 :: Local < v8 :: String > s = Nan :: New ( "test" ); Nan :: Utf8String string ( s ); Nan :: Callback * listener = new Nan :: Callback ( info [ 1 ]. As < v8 :: Function >());
五、一些帮助文档
https://github.com/nodejs/nan 《Node.js 来一打C++扩展》死月 著 https://v8docs.nodesource.com/node-14.15/index.html https://v8.dev/ https://www.w3schools.com/cpp/