转贴:http://www.voidcn.com/article/p-uxrmuzuv-yv.html
再续前文,前文介绍了node.js 的addon用法和google v8 引擎,下面,我们进入真正的编码,下面将会通过六个例子,学习node addon 范例,了解addon编程的特性
http://blog.whattoc.com/2013/09/08/nodejs_api_addon_3/
- 创建一个空项目
- 随机数模块
- 向模块传递参数
- 回调函数处理
- 线程处理
- 对象管理
创建一个空项目
vi modulename.cpp
1
2
3
4
5
6
7
8 1#include <node.h>
2void RegisterModule(v8::Handle<v8::Object> target) {
3 // 注册模块功能,负责导出接口到node.js
4}
5// 注册模块名称,编译后,模块将编译成modulename.node文件
6// 当你需要修改模块名字的时候,需要修改 binding.gyp("target_name") 和此处
7NODE_MODULE(modulename, RegisterModule);
8
vi binding.gyp
1
2
3 1{
2 "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }
3
vi run.js
1
2
3 1var modulename = require('./build/Release/modulename');
2console.warn(modulename);
3
编译addon模块,编译器前,需要确保安装好node-gyp
1
2
3
4
5
6 1#npm node-gyp -g
2#node-gyp configure
3#node-gyp build
4#node run.js
5{}
6
随机数模块实现
vi modulename.cpp
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 1#include <node.h>
2// 标准C库
3#include <cstdlib>
4#include <ctime>
5using namespace v8; // 函数返回javascript格式的 0 或 1 Handle<Value> Random(const Arguments& args) {
6
7 // 在每一个功能函数中,都会看到 Handle scope,主要是存放handle的容器
8 // 管理handle的内存空间,当handle scope被销毁的时候
9 // 内存将会释放,否则内存将会泄漏
10
11 HandleScope scope;
12
13 // 确保函数中,在调用scope.Close()前都完成了出来
14 // 当执行完scope.Close后,handle将会被清空。
15
16 return scope.Close(
17 // 将rand() % 2 的C语言int运行结果,转换成javascript类型的结果
18 Integer::New(rand() % 2)
19 );
20}
21void RegisterModule(Handle<Object> target) {
22 srand(time(NULL));
23 // target 是 .node 文件导出的模块
24 target->Set(String::NewSymbol("random"),
25 FunctionTemplate::New(Random)->GetFunction());
26}
27NODE_MODULE(modulename, RegisterModule);
28
HandleScope scope,申请一个装载Handle(Local)的容器,当生命周期结束时,即scope.Close()的时候,Handle的内容就会被释放。Integer::New(rand() % 2),rand()是C语言编写的函数,产生的数值是C语言类型的,如果node模块需要调用,必须转换格式,Integer::New() 整形转换。最后,函数声明的返回类型是Handle
String, Number, Boolean, Object, Array 继承了Value,想了解更多关于数据类型的转换可以参考《Node.js C++ addon编写实战(二)之对象转换》
vi binding.gyp
1
2
3 1{
2 "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }
3
vi run.js
1
2
3 1var modulename = require('./build/Release/modulename');
2console.warn(modulename.random());
3
编译执行
1
2
3
4
5 1#node-gyp configure
2#node-gyp build
3#node run.js
41 //or 0
5
函数参数传入
vi modulename.cpp
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 1#include <node.h>
2using namespace v8; // 返回 第N个数 的斐波拉契数列 // argument passed. Handle<Value> Fibonacci(const Arguments& args) {
3 HandleScope scope;
4 //检查参数个数问题
5 if (args.Length() < 1) {
6 return ThrowException(
7 Exception::TypeError(String::New("First argument must be a number"))
8 );
9 }
10 //将javascript格式转换成C语言可以使用的int32_t类型
11 Local<Integer> integer = args[0]->ToInteger();
12 int32_t seq = integer->Value();
13 // 检测seq是否在正确值域上
14 if (seq < 0) {
15 return ThrowException(Exception::TypeError(String::New(
16 "Fibonacci sequence number must be positive")));
17 }
18 // 执行算法
19 int32_t current = 1;
20 for (int32_t previous = -1, next = 0, i = 0; i <= seq; i++) {
21 next = previous + current;
22 previous = current;
23 current = next;
24 }
25 // 返回数值
26 return scope.Close(Integer::New(current));
27}
28void RegisterModule(Handle<Object> target) {
29 target->Set(String::NewSymbol("fibonacci"),
30 FunctionTemplate::New(Fibonacci)->GetFunction());
31}
32NODE_MODULE(modulename, RegisterModule);
33
vi binding.gyp
1
2
3 1{
2 "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }
3
参数传入的情况,少不参数的合法性校验和类型转换,总结一下几种转换和类型合法性的检测
类型判断
1
2
3
4
5
6 1Local<Value> arg = args[0];
2bool isArray = arg->IsArray();
3bool isBoolean = arg->IsBoolean();
4bool isNumber = arg->IsNumber();
5bool isInt32 = arg->IsInt32();
6
类型转换
1
2
3
4
5
6
7 1Local<Value> arg = args[0];
2Local<Object> = arg->ToObject();
3Local<Boolean> = arg->ToBoolean();
4Local<Number> = arg->ToNumber();
5Local<Int32> = arg->ToInt32 ();
6Local<Function> callback = Local<Function>::Cast(args)
7
vi run.js
1
2
3 1var modulename = require('./build/Release/modulename');
2console.warn(modulename.fibonacci(9));
3
编译执行
1
2
3
4
5 1#node-gyp configure
2#node-gyp build
3#node run.js
432
5
回调函数处理
vi modulename.cpp
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
35
36
37
38
39
40
41
42
43
44
45 1#include <node.h>
2using namespace v8;
3Handle<Value> Callback(const Arguments& args) {
4 HandleScope scope;
5 // Ensure that we got a callback. Generally, your functions should have
6 // optional callbacks. In this case, you can declare an empty
7 // Local<Function> handle and check for content before calling.
8
9 //确保参数是回调函数,是函数
10 if (!args[1]->IsFunction()) {
11 return ThrowException(Exception::TypeError(
12 String::New("Second argument must be a callback function")));
13 }
14 // 强制转换成函数
15 Local<Function> callback = Local<Function>::Cast(args[1]);
16 // node.js 默认第一个参数是错误情况,第二个参数是回调函数或参与工作的参数
17 bool error = args[0]->BooleanValue();
18 if (error) {
19 Local<Value> err = Exception::Error(String::New("Something went wrong!"));
20 // 为返回的错误,创建更多的属性,数值23
21 err->ToObject()->Set(NODE_PSYMBOL("errno"), Integer::New(23));
22 // 定义回调函数的参数个数和参数数组
23 const unsigned argc = 1;
24 Local<Value> argv[argc] = { err };
25 // 异步回调执行 callback
26 callback->Call(Context::GetCurrent()->Global(), argc, argv);
27 } else {
28
29 // 如果执行成功,Node.js的惯例会将第一个参数设置成null
30 const unsigned argc = 2;
31 Local<Value> argv[argc] = {
32 Local<Value>::New(Null()),
33 Local<Value>::New(Integer::New(42))
34 };
35 // 异步回调执行 callback
36 callback->Call(Context::GetCurrent()->Global(), argc, argv);
37 }
38 return Undefined(); //异步函数,可以立即返回
39}
40void RegisterModule(Handle<Object> target) {
41 target->Set(String::NewSymbol("callback"),
42 FunctionTemplate::New(Callback)->GetFunction());
43}
44NODE_MODULE(modulename, RegisterModule);
45
Node.js的回调函数设计,约定俗成,callback(err, status) 第一参数默认是错误状态,第二个是需要回调的数值,当成功执行的时候,错误状态err设置成null。
vi binding.gyp
1
2
3 1{
2 "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }
3
vi run.js
1
2
3
4
5 1var modulename = require('./build/Release/modulename');
2modulename.callback(false, function(err, result) {
3 console.warn(result);
4});
5
编译完成
1
2
3
4
5 1#node-gyp configure
2#node-gyp build
3#node run.js
442
5
线程控制
vi modulename.cpp
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 1#include <node.h>
2#include <string>
3using namespace v8;
4// 声明函数
5Handle<Value> Async(const Arguments& args);
6void AsyncWork(uv_work_t* req);
7void AsyncAfter(uv_work_t* req);
8// 构建一个结构体存储异步工作的请求信息
9struct Baton {
10
11 //存放回调函数,使用Persistent来声明,让系统不会在函数结束后自动回收
12 //当回调成功后,需要执行dispose释放空间
13 Persistent<Function> callback;
14 // 错误控制,保护错误信息和错误状态
15 bool error;
16 std::string error_message;
17 // 参与运行的参数
18 int32_t result;
19};
20
21
22// 函数会在Node.js当中直接被调用,它创建了一个请求的对象和等待的时间表
23Handle<Value> Async(const Arguments& args) {
24 HandleScope scope;
25 if (!args[0]->IsFunction()) {
26 return ThrowException(Exception::TypeError(
27 String::New("First argument must be a callback function")));
28 }
29 // 强制将参数转换成 函数变量
30 Local<Function> callback = Local<Function>::Cast(args[0]);
31
32
33 // Baton 负责管理的异步操作的状态信息,作为参数带进去 异步回调数据的流程中
34 Baton* baton = new Baton();
35 baton->error = false;
36 baton->callback = Persistent<Function>::New(callback);
37 uv_work_t *req = new uv_work_t();
38 req->data = baton;
39
40 // 通过libuv进行操作的异步等待,可以通过libuv定义一个函数,当函数执行完成后回调
41 // 在此可以定义需要处理的异步流程AsyncWork和执行完异步流程后的操作AsyncAfter
42 int status = uv_queue_work(uv_default_loop(), req, AsyncWork,
43 (uv_after_work_cb)AsyncAfter);
44 assert(status == 0);
45 return Undefined();
46}
47// 注意不能使用 google v8的特性,需要用原生的C/C++来写
48// 因为它是调用另外一个线程去执行任务。
49
50void AsyncWork(uv_work_t* req) {
51 Baton* baton = static_cast<Baton*>(req->data);
52 // 执行线程池中的工作
53 baton->result = 42;
54
55 如果线程中出现错误,我们可以设置baton->error_message 来记录错误的字符串 和 baton-error = ture
56}
57//执行完任务,进行回调的时候,返回到 v8/javascript的线程
58//这就意味着我们需要利用HandleScope来管理空间
59void AsyncAfter(uv_work_t* req) {
60 HandleScope scope;
61 Baton* baton = static_cast<Baton*>(req->data);
62 if (baton->error) {
63 Local<Value> err = Exception::Error(String::New(baton->error_message.c_str()));
64 // 准备回调函数的参数
65 const unsigned argc = 1;
66 Local<Value> argv[argc] = { err };
67
68 // 用类似 try catach的方法,捕捉回调中的错误,在Node环境中
69 //可以利用process.on('uncaughtException')捕捉错误
70 TryCatch try_catch;
71
72 baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
73 if (try_catch.HasCaught()) {
74 node::FatalException(try_catch);
75 }
76 } else {
77 const unsigned argc = 2;
78 Local<Value> argv[argc] = {
79 Local<Value>::New(Null()),
80 Local<Value>::New(Integer::New(baton->result))
81 };
82 TryCatch try_catch;
83 baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
84 if (try_catch.HasCaught()) {
85 node::FatalException(try_catch);
86 }
87 }
88
89 baton->callback.Dispose();
90 // 处理完毕,清除对象和空间
91 delete baton;
92 delete req;
93}
94void RegisterModule(Handle<Object> target) {
95 target->Set(String::NewSymbol("async"),
96 FunctionTemplate::New(Async)->GetFunction());
97}
98
99NODE_MODULE(modulename, RegisterModule);
100
与Persistent<Function>::New(callback)与Local<Function>相对,Local的内存,会随着Handle Scope的释放而释放,而Persistent则需要手动执行Dispose()方法才能释放,如果没有执行,内存则会长期备用,但同时他为回调带来了方便,即使主函数结束,执行了scope.Close后,依然能够很好地工作,uv_queue_work是libuv库的函数,libuv是Node.js异步操作的关键,关于库的描述,已经超出了本节的描述范围,详细可以参考libuv,异步的过程中,处理函数已经切出了addon模块的上下文,只能通过baton来传递状态信息,更加不能使用google v8的函数,回调成功后,利用v8的转换函数,转换成模块需要使用的数据格式返回。
vi binding.gyp
1
2
3 1{
2 "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }
3
vi run.js
1
2
3
4
5 1var modulename = require('./build/Release/modulename');
2modulename.async(function(err, result) {
3 console.warn(result);
4});
5
编译完成
1
2
3
4
5 1#node-gyp configure
2#node-gyp build
3#node run.js
442
5
对象处理
vi moudulename.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1#ifndef MODULENAME_HPP
2#define MODULENAME_HPP
3#include <node.h>
4class MyObject : public node::ObjectWrap {
5public:
6 static v8::Persistent<v8::FunctionTemplate> constructor;
7 static void Init(v8::Handle<v8::Object> target);
8protected:
9 MyObject(int val);
10 static v8::Handle<v8::Value> New(const v8::Arguments& args);
11 static v8::Handle<v8::Value> Value(const v8::Arguments& args);
12 int value_;
13};
14#endif
15
vi modulename.cpp
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 1#include <node.h>
2#include "modulename.hpp"
3using namespace v8; Persistent<FunctionTemplate> MyObject::constructor; void MyObject::Init(Handle<Object> target) {
4 HandleScope scope;
5 Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
6 Local<String> name = String::NewSymbol("MyObject");
7 constructor = Persistent<FunctionTemplate>::New(tpl);
8
9 // 设置对象属性的数量为1和属性名字
10 constructor->InstanceTemplate()->SetInternalFieldCount(1);
11 constructor->SetClassName(name);
12 // 添加属性value 和对应的数值Value
13 NODE_SET_PROTOTYPE_METHOD(constructor, "value", Value);
14 target->Set(name, constructor->GetFunction());
15}
16// MyObject的构造函数
17MyObject::MyObject(int val)
18 : ObjectWrap(),
19 value_(val) {}
20// MyObject 继承node::ObjectWrap,复写New
21// 以便在node中运行 var obj = new modulename.MyObject(42)的时候,初始化MyObject
22Handle<Value> MyObject::New(const Arguments& args) {
23 HandleScope scope;
24 if (!args.IsConstructCall()) {
25 return ThrowException(Exception::TypeError(
26 String::New("Use the new operator to create instances of this object."))
27 );
28 }
29 if (args.Length() < 1) {
30 return ThrowException(Exception::TypeError(
31 String::New("First argument must be a number")));
32 }
33 // Creates a new instance object of this type and wraps it.
34 MyObject* obj = new MyObject(args[0]->ToInteger()->Value());
35 obj->Wrap(args.This());
36 return args.This();
37}
38// 定义Value
39Handle<Value> MyObject::Value(const Arguments& args) {
40 HandleScope scope;
41 // Retrieves the pointer to the wrapped object instance.
42 MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This());
43 return scope.Close(Integer::New(obj->value_));
44}
45void RegisterModule(Handle<Object> target) {
46 MyObject::Init(target);
47}
48NODE_MODULE(modulename, RegisterModule);
49
本例子,为我们演示了,如果在模块中使用类和对象的重构,利用obj->Wrap(args.This());向模块暴漏接口,再利用ObjectWrap::Unwrap<MyObject>(args.This());解释传递对象的接口。
vi binding.gyp
1
2
3 1{
2 "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }
3
vi run.js
1
2
3
4
5 1var modulename = require('./build/Release/modulename');
2var obj = new modulename.MyObject(42);
3console.warn(obj);
4console.warn(obj.value());
5
编译完成
1
2
3
4
5
6 1#node-gyp configure
2#node-gyp build
3#node run.js
4{}
542
6
参考资料
https://github.com/kkaefer/node-cpp-modules
http://deadhorse.me/nodejs/2012/10/08/c_addon_in_nodejs_node_gyp.html