在node.js项目中使用c++ addons

藉由v8的强力支持,我们可以在node.js中可以很方便的使用一些c++库,或者自己编写一个动态链接库,并借v8库的连接,使之成为一个和其他node module一样可以直接通过require调用的方法.编写原生c++模块,可以地处理CPU密集型的操作,我们在日常使用中,也可以看到很多c++编写的npm包,如进行串口通讯用的包serialport、进行蓝牙通讯的包noble等等。

addons使用基本流程

  1. 编写c++源文件
  2. 创建binding.gyp文件
  3. 开始构建编译
    1. node-gyp configure(执行后目录下会多处build文件夹)
    2. node-gyp build (执行后build目录下回产生Release文件夹)
  4. require编译后的原声模块,在js中调用原生方法
1.1 基本用法

hello.cc源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <node.h>
#include <v8.h>
using namespace v8;

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

// 模块初始化函数,这里模块中创建一个名叫的hello的方法
void init(Local<Object> exports) {

/*
// 这样hello将成为导出模块的一个属性
//如果想设置Method作为到处模块本身,可以这样写:
void Init(Local<Object> exports, Local<Object> module) {
NODE_SET_METHOD(module, "exports", CreateObject);
}
*/
NODE_SET_METHOD(exports, "hello", Method);
}

//NODE_MODULE 是一个宏,export一个名叫addon的模块,init为初始化函数名
NODE_MODULE(addon, init)

binding.gyp:

1
2
3
4
5
6
7
8
{
"targets": [
{
"target_name": "addon",
"sources": [ "hello.cc" ]
}
]
}

调用测试脚本,test.js

1
2
3
4
5
// hello.js

const addon = require('./build/Release/addon');
console.log(addon.hello());
// Prints: 'world'

addons同步方法用法

js是一种弱类型语言,对数组/对象的使用都十分灵活,但是c++在这方面的灵活性就弱很多,变量的类型和
复合类型的使用都必须严格规定。同时,根据我们使用的场景,这里我列出了如下几种常见的调用方法类型。

  1. 单个简单类型如参,根据参数类型返回对应类型的返回值。
  2. 传入js函数,并同步执行
  3. 传入js对象,处理后返回新对象
简单类型参数处理和返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 单个入参,根据不同入参类型,返回不同类型值
void test_one_param(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();

if (args[0]->IsNumber()){ // 处理数字类型入参并返回数字类型
double a = args[0]->NumberValue(); //获取数字入参的方法
Local<Number> return_val = v8::Number::New(isolate,a+1);
//Set the return value (using the passed in FunctionCallbackInfo<Value>&)
args.GetReturnValue().Set(return_val);
return;
}
else if (args[0]->IsBoolean()){ //处理布尔类型入参数,并返回布尔类型
bool a = args[0]->BooleanValue(); // 获取布尔类型入参的方法
Local<Boolean> return_val = v8::Boolean::New(isolate,!a);
args.GetReturnValue().Set(return_val);
return;
} else if (args[0]->IsString()){ //处理字符串类型对象并返回字符串对象
Local<String> add_str = v8::String::NewFromUtf8(isolate,"eric");
add_str = v8::String::Concat(args[0]->ToString(),add_str); // 获取字符入参的方法
args.GetReturnValue().Set(add_str);
return;
} else {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong argument type")));
return;
}
}
处理函数类型入参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//创建用户返回的原生函数
void cb(const FunctionCallbackInfo<v8::Value>& args) {
Isolate * isolate = args.GetIsolate();
Local<String> a = args[0]->ToString();

args.GetReturnValue().Set(v8::String::Concat(String::NewFromUtf8(isolate, ",hello"),a));
}

//传入函数,并执行
void method(const FunctionCallbackInfo<Value> &args){
Isolate * isolate = args.GetIsolate();

if(args[0]->IsFunction()){
Local<Function> js_fn = Local<Function>::Cast(args[0]); //处理函数类型入参
for(int i=0;i<11;i++){
printf("%d\n", i);
}
//第2,3个参数表示函数参数个数,和参数数组指针
js_fn->Call(v8::Null(isolate),0,NULL); // 执行js脚本传入的函数

//处理带参数函数执行用法如下,表示函数有一个入参
// const unsigned argc = 1;
// Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "parameter1") };
// cb->Call(Null(isolate), argc, argv);

//返回一个原生函数
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, cb);
Local<Function> fn = tpl->GetFunction();
// omit this to make it anonymous
fn->SetName(String::NewFromUtf8(isolate, "theFunction"));

args.GetReturnValue().Set(fn);
}
}
处理对象类型入参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//传入对象类型,并返回一个js对象
void process_obj(const FunctionCallbackInfo<Value>& args ) {
v8::Isolate * isolate = args.GetIsolate();

Local<Object> ret_obj = Object::New(isolate); //定义一个对象用于返回
// 返回对象增加一个name属性,属性值为"process_obj"
ret_obj->Set(v8::String::NewFromUtf8(isolate,"name"),v8::String::NewFromUtf8(isolate,"process_obj"));

//处理传入的对象,获取对象属性列表存入数组,并作为返回对象的input_pros对象返回
if(args[0]->IsObject()) {
Local<Object> input_obj = args[0]->ToObject();
Local<Array> keys = input_obj->GetOwnPropertyNames();
ret_obj->Set(v8::String::NewFromUtf8(isolate,"input_pros"),keys);
}

// 返回一个新的对象
args.GetReturnValue().Set(ret_obj);
}

addons异步模块创建

之前的原生模块都是同步模块,调用后立即执行,调用者会一直等待被调用函数执行完成。如果我们想实现异步回调,就要借助libuv的强大魔力了。
libuv强制使用异步、事件驱动的编程风格。它的核心工作是提供一个event-loop,还有基于I/O和其它事件通知的回调函数。libuv还提供了一些核心工具,例如定时器,非阻塞的网络支持,异步文件系统访问,子进程等。
这里我们使用libuv的uv_default_loop来创建一个默认的事件循环.uv_queue_work()是一个便利的函数,它使得一个应用程序能够在不同的线程运行任务,当任务完成后,回调函数将会被触发.
后续会补上详细的libuv库和异步模块的使用笔记。
— 未完待续😢

这里,参考一下google到的资料,记录了一下c++addons的常规用法,便于后面自己使用的时候来查看。
参考1(英文)
参考2(英文)
参考3(中文)
参考4(中文)
libuv官方文档