详解Node.js API系列C/C++ Addons(3) 程序实例

释放双眼,带上耳机,听听看~!

转贴: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  &quot;targets&quot;: [ { &quot;target_name&quot;: &quot;modulename&quot;, &quot;sources&quot;: [ &quot;modulename.cpp&quot; ] } ] }
3

vi run.js


1
2
3
4
5
1var modulename = require(&#x27;./build/Release/modulename&#x27;);
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 &lt;node.h&gt;
4class MyObject : public node::ObjectWrap {
5public:
6    static v8::Persistent&lt;v8::FunctionTemplate&gt; constructor;
7    static void Init(v8::Handle&lt;v8::Object&gt; target);
8protected:
9    MyObject(int val);
10    static v8::Handle&lt;v8::Value&gt; New(const v8::Arguments&amp; args);
11    static v8::Handle&lt;v8::Value&gt; Value(const v8::Arguments&amp; 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 &lt;node.h&gt;
2#include &quot;modulename.hpp&quot;
3using namespace v8; Persistent&lt;FunctionTemplate&gt; MyObject::constructor; void MyObject::Init(Handle&lt;Object&gt; target) {
4    HandleScope scope;
5    Local&lt;FunctionTemplate&gt; tpl = FunctionTemplate::New(New);
6    Local&lt;String&gt; name = String::NewSymbol(&quot;MyObject&quot;);
7    constructor = Persistent&lt;FunctionTemplate&gt;::New(tpl);
8
9    // 设置对象属性的数量为1和属性名字
10    constructor-&gt;InstanceTemplate()-&gt;SetInternalFieldCount(1);
11    constructor-&gt;SetClassName(name);
12    // 添加属性value 和对应的数值Value
13    NODE_SET_PROTOTYPE_METHOD(constructor, &quot;value&quot;, Value);
14    target-&gt;Set(name, constructor-&gt;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&lt;Value&gt; MyObject::New(const Arguments&amp; args) {
23    HandleScope scope;
24    if (!args.IsConstructCall()) {
25        return ThrowException(Exception::TypeError(
26            String::New(&quot;Use the new operator to create instances of this object.&quot;))
27        );
28    }
29    if (args.Length() &lt; 1) {
30        return ThrowException(Exception::TypeError(
31            String::New(&quot;First argument must be a number&quot;)));
32    }
33    // Creates a new instance object of this type and wraps it.
34    MyObject* obj = new MyObject(args[0]-&gt;ToInteger()-&gt;Value());
35    obj-&gt;Wrap(args.This());
36    return args.This();
37}
38// 定义Value
39Handle&lt;Value&gt; MyObject::Value(const Arguments&amp; args) {
40    HandleScope scope;
41    // Retrieves the pointer to the wrapped object instance.
42    MyObject* obj = ObjectWrap::Unwrap&lt;MyObject&gt;(args.This());
43    return scope.Close(Integer::New(obj-&gt;value_));
44}
45void RegisterModule(Handle&lt;Object&gt; 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  &quot;targets&quot;: [ { &quot;target_name&quot;: &quot;modulename&quot;, &quot;sources&quot;: [ &quot;modulename.cpp&quot; ] } ] }
3

vi run.js


1
2
3
4
5
1var modulename = require(&#x27;./build/Release/modulename&#x27;);
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

转贴:http://www.voidcn.com/article/p-uxrmuzuv-yv.html

给TA打赏
共{{data.count}}人
人已打赏
安全技术

【JWT】JWT+HA256加密 Token验证

2021-8-18 16:36:11

安全技术

C++ 高性能服务器网络框架设计细节

2022-1-11 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索