一、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/