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

再续前文,前文介绍了node.js 的addon用法和google v8 引擎,下面,我们进入真正的编码,下面将会通过六个例子,学习node addon 范例,了解addon编程的特性

http://blog.whattoc.com/2013/09/08/nodejs_api_addon_3/

  • 创建一个空项目
  • 随机数模块
  • 向模块传递参数
  • 回调函数处理
  • 线程处理
  • 对象管理

创建一个空项目

vi modulename.cpp

#include <node.h>
void RegisterModule(v8::Handle<v8::Object> target) {
    // 注册模块功能,负责导出接口到node.js
}
// 注册模块名称,编译后,模块将编译成modulename.node文件
// 当你需要修改模块名字的时候,需要修改 binding.gyp("target_name") 和此处
NODE_MODULE(modulename, RegisterModule);

vi binding.gyp

{
  "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }

vi run.js

var modulename = require('./build/Release/modulename');
console.warn(modulename);

编译addon模块,编译器前,需要确保安装好node-gyp

#npm node-gyp -g
#node-gyp configure
#node-gyp build
#node run.js
{}

随机数模块实现

vi modulename.cpp

#include <node.h>
// 标准C库
#include <cstdlib>
#include <ctime>
using namespace v8; // 函数返回javascript格式的 0 或 1 Handle<Value> Random(const Arguments& args) {

    // 在每一个功能函数中,都会看到 Handle scope,主要是存放handle的容器
    // 管理handle的内存空间,当handle scope被销毁的时候
    // 内存将会释放,否则内存将会泄漏

    HandleScope scope;

    // 确保函数中,在调用scope.Close()前都完成了出来
    // 当执行完scope.Close后,handle将会被清空。

    return scope.Close(
        // 将rand() % 2 的C语言int运行结果,转换成javascript类型的结果
        Integer::New(rand() % 2)
    );
}
void RegisterModule(Handle<Object> target) {
    srand(time(NULL));
    // target 是 .node 文件导出的模块
    target->Set(String::NewSymbol("random"),
        FunctionTemplate::New(Random)->GetFunction());
}
NODE_MODULE(modulename, RegisterModule);

HandleScope scope,申请一个装载Handle(Local)的容器,当生命周期结束时,即scope.Close()的时候,Handle的内容就会被释放。Integer::New(rand() % 2),rand()是C语言编写的函数,产生的数值是C语言类型的,如果node模块需要调用,必须转换格式,Integer::New() 整形转换。最后,函数声明的返回类型是Handle

详解Node.js API系列C/C++ Addons(3) 程序实例_第1张图片

String, Number, Boolean, Object, Array 继承了Value,想了解更多关于数据类型的转换可以参考《Node.js C++ addon编写实战(二)之对象转换》

vi binding.gyp

{
  "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }

vi run.js

var modulename = require('./build/Release/modulename');
console.warn(modulename.random());

编译执行

#node-gyp configure
#node-gyp build
#node run.js
1 //or 0

函数参数传入

vi modulename.cpp

#include <node.h>
using namespace v8; // 返回 第N个数 的斐波拉契数列 // argument passed. Handle<Value> Fibonacci(const Arguments& args) {
    HandleScope scope;
    //检查参数个数问题
    if (args.Length() < 1) {
        return ThrowException(
            Exception::TypeError(String::New("First argument must be a number"))
        );
    }
    //将javascript格式转换成C语言可以使用的int32_t类型
    Local<Integer> integer = args[0]->ToInteger();
    int32_t seq = integer->Value();
    // 检测seq是否在正确值域上
    if (seq < 0) {
        return ThrowException(Exception::TypeError(String::New(
            "Fibonacci sequence number must be positive")));
    }
    // 执行算法
    int32_t current = 1;
    for (int32_t previous = -1, next = 0, i = 0; i <= seq; i++) {
        next = previous + current;
        previous = current;
        current = next;
    }
    // 返回数值
    return scope.Close(Integer::New(current));
}
void RegisterModule(Handle<Object> target) {
    target->Set(String::NewSymbol("fibonacci"),
        FunctionTemplate::New(Fibonacci)->GetFunction());
}
NODE_MODULE(modulename, RegisterModule);

vi binding.gyp

{
  "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }

参数传入的情况,少不参数的合法性校验和类型转换,总结一下几种转换和类型合法性的检测

类型判断

Local<Value> arg = args[0];
bool isArray = arg->IsArray();
bool isBoolean = arg->IsBoolean();
bool isNumber = arg->IsNumber();
bool isInt32 = arg->IsInt32();

类型转换

Local<Value> arg = args[0];
Local<Object> = arg->ToObject();
Local<Boolean> = arg->ToBoolean();
Local<Number> = arg->ToNumber();
Local<Int32> = arg->ToInt32 ();
Local<Function> callback = Local<Function>::Cast(args)

vi run.js

var modulename = require('./build/Release/modulename');
console.warn(modulename.fibonacci(9));

编译执行

#node-gyp configure
#node-gyp build
#node run.js
32

回调函数处理

vi modulename.cpp

#include <node.h>
using namespace v8;
Handle<Value> Callback(const Arguments& args) {
    HandleScope scope;
    // Ensure that we got a callback. Generally, your functions should have
    // optional callbacks. In this case, you can declare an empty
    // Local<Function> handle and check for content before calling.

    //确保参数是回调函数,是函数
    if (!args[1]->IsFunction()) {
        return ThrowException(Exception::TypeError(
            String::New("Second argument must be a callback function")));
    }
    // 强制转换成函数
    Local<Function> callback = Local<Function>::Cast(args[1]);
    // node.js 默认第一个参数是错误情况,第二个参数是回调函数或参与工作的参数
    bool error = args[0]->BooleanValue();
    if (error) {
        Local<Value> err = Exception::Error(String::New("Something went wrong!"));
        // 为返回的错误,创建更多的属性,数值23
        err->ToObject()->Set(NODE_PSYMBOL("errno"), Integer::New(23));
        // 定义回调函数的参数个数和参数数组
        const unsigned argc = 1;
        Local<Value> argv[argc] = { err };
        // 异步回调执行 callback 
        callback->Call(Context::GetCurrent()->Global(), argc, argv);
    } else {

        // 如果执行成功,Node.js的惯例会将第一个参数设置成null
        const unsigned argc = 2;
        Local<Value> argv[argc] = {
            Local<Value>::New(Null()),
            Local<Value>::New(Integer::New(42))
        };
        // 异步回调执行 callback 
        callback->Call(Context::GetCurrent()->Global(), argc, argv);
    }
    return Undefined(); //异步函数,可以立即返回
}
void RegisterModule(Handle<Object> target) {
    target->Set(String::NewSymbol("callback"),
        FunctionTemplate::New(Callback)->GetFunction());
}
NODE_MODULE(modulename, RegisterModule);

Node.js的回调函数设计,约定俗成,callback(err, status) 第一参数默认是错误状态,第二个是需要回调的数值,当成功执行的时候,错误状态err设置成null。

vi binding.gyp

{
  "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }

vi run.js

var modulename = require('./build/Release/modulename');
modulename.callback(false, function(err, result) {
    console.warn(result);
});

编译完成

#node-gyp configure
#node-gyp build
#node run.js
42

线程控制

vi modulename.cpp

#include <node.h>
#include <string>
using namespace v8;
// 声明函数
Handle<Value> Async(const Arguments& args);
void AsyncWork(uv_work_t* req);
void AsyncAfter(uv_work_t* req);
// 构建一个结构体存储异步工作的请求信息
struct Baton {

    //存放回调函数,使用Persistent来声明,让系统不会在函数结束后自动回收
    //当回调成功后,需要执行dispose释放空间
    Persistent<Function> callback;
    // 错误控制,保护错误信息和错误状态
    bool error;
    std::string error_message;
    // 参与运行的参数
    int32_t result;
};


// 函数会在Node.js当中直接被调用,它创建了一个请求的对象和等待的时间表
Handle<Value> Async(const Arguments& args) {
    HandleScope scope;
    if (!args[0]->IsFunction()) {
        return ThrowException(Exception::TypeError(
            String::New("First argument must be a callback function")));
    }
    // 强制将参数转换成 函数变量
    Local<Function> callback = Local<Function>::Cast(args[0]);


    // Baton 负责管理的异步操作的状态信息,作为参数带进去 异步回调数据的流程中
    Baton* baton = new Baton();
    baton->error = false;
    baton->callback = Persistent<Function>::New(callback);
    uv_work_t *req = new uv_work_t();
    req->data = baton;

    // 通过libuv进行操作的异步等待,可以通过libuv定义一个函数,当函数执行完成后回调
    // 在此可以定义需要处理的异步流程AsyncWork和执行完异步流程后的操作AsyncAfter
    int status = uv_queue_work(uv_default_loop(), req, AsyncWork,
                               (uv_after_work_cb)AsyncAfter);
    assert(status == 0);
    return Undefined();
}
// 注意不能使用 google v8的特性,需要用原生的C/C++来写
// 因为它是调用另外一个线程去执行任务。

void AsyncWork(uv_work_t* req) {
    Baton* baton = static_cast<Baton*>(req->data);
    // 执行线程池中的工作
    baton->result = 42;

    如果线程中出现错误,我们可以设置baton->error_message 来记录错误的字符串 和 baton-error = ture
}
//执行完任务,进行回调的时候,返回到 v8/javascript的线程
//这就意味着我们需要利用HandleScope来管理空间
void AsyncAfter(uv_work_t* req) {
    HandleScope scope;
    Baton* baton = static_cast<Baton*>(req->data);
    if (baton->error) {
        Local<Value> err = Exception::Error(String::New(baton->error_message.c_str()));
        // 准备回调函数的参数
        const unsigned argc = 1;
        Local<Value> argv[argc] = { err };

        // 用类似 try catach的方法,捕捉回调中的错误,在Node环境中
        //可以利用process.on('uncaughtException')捕捉错误 
        TryCatch try_catch;

        baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
        if (try_catch.HasCaught()) {
            node::FatalException(try_catch);
        }
    } else {
        const unsigned argc = 2;
        Local<Value> argv[argc] = {
            Local<Value>::New(Null()),
            Local<Value>::New(Integer::New(baton->result))
        };
        TryCatch try_catch;
        baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
        if (try_catch.HasCaught()) {
            node::FatalException(try_catch);
        }
    }

    baton->callback.Dispose();
    // 处理完毕,清除对象和空间
    delete baton;
    delete req;
}
void RegisterModule(Handle<Object> target) {
    target->Set(String::NewSymbol("async"),
        FunctionTemplate::New(Async)->GetFunction());
}

NODE_MODULE(modulename, RegisterModule);

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

{
  "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }

vi run.js

var modulename = require('./build/Release/modulename');
modulename.async(function(err, result) {
    console.warn(result);
});

编译完成

#node-gyp configure
#node-gyp build
#node run.js
42

对象处理

vi moudulename.hpp

#ifndef MODULENAME_HPP
#define MODULENAME_HPP
#include <node.h>
class MyObject : public node::ObjectWrap {
public:
    static v8::Persistent<v8::FunctionTemplate> constructor;
    static void Init(v8::Handle<v8::Object> target);
protected:
    MyObject(int val);
    static v8::Handle<v8::Value> New(const v8::Arguments& args);
    static v8::Handle<v8::Value> Value(const v8::Arguments& args);
    int value_;
};
#endif

vi modulename.cpp

#include <node.h>
#include "modulename.hpp"
using namespace v8; Persistent<FunctionTemplate> MyObject::constructor; void MyObject::Init(Handle<Object> target) {
    HandleScope scope;
    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
    Local<String> name = String::NewSymbol("MyObject");
    constructor = Persistent<FunctionTemplate>::New(tpl);

    // 设置对象属性的数量为1和属性名字
    constructor->InstanceTemplate()->SetInternalFieldCount(1);
    constructor->SetClassName(name);
    // 添加属性value 和对应的数值Value
    NODE_SET_PROTOTYPE_METHOD(constructor, "value", Value);
    target->Set(name, constructor->GetFunction());
}
// MyObject的构造函数
MyObject::MyObject(int val)
    : ObjectWrap(),
      value_(val) {}
// MyObject 继承node::ObjectWrap,复写New
// 以便在node中运行 var obj = new modulename.MyObject(42)的时候,初始化MyObject
Handle<Value> MyObject::New(const Arguments& args) {
    HandleScope scope;
    if (!args.IsConstructCall()) {
        return ThrowException(Exception::TypeError(
            String::New("Use the new operator to create instances of this object."))
        );
    }
    if (args.Length() < 1) {
        return ThrowException(Exception::TypeError(
            String::New("First argument must be a number")));
    }
    // Creates a new instance object of this type and wraps it.
    MyObject* obj = new MyObject(args[0]->ToInteger()->Value());
    obj->Wrap(args.This());
    return args.This();
}
// 定义Value
Handle<Value> MyObject::Value(const Arguments& args) {
    HandleScope scope;
    // Retrieves the pointer to the wrapped object instance.
    MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This());
    return scope.Close(Integer::New(obj->value_));
}
void RegisterModule(Handle<Object> target) {
    MyObject::Init(target);
}
NODE_MODULE(modulename, RegisterModule);

本例子,为我们演示了,如果在模块中使用类和对象的重构,利用obj->Wrap(args.This());向模块暴漏接口,再利用ObjectWrap::Unwrap<MyObject>(args.This());解释传递对象的接口。

vi binding.gyp

{
  "targets": [ { "target_name": "modulename", "sources": [ "modulename.cpp" ] } ] }

vi run.js

var modulename = require('./build/Release/modulename');
var obj = new modulename.MyObject(42);
console.warn(obj);
console.warn(obj.value());

编译完成

#node-gyp configure
#node-gyp build
#node run.js
{}
42

参考资料

https://github.com/kkaefer/node-cpp-modules
http://deadhorse.me/nodejs/2012/10/08/c_addon_in_nodejs_node_gyp.html

你可能感兴趣的:(node.js)