http://blog.whattoc.com/2013/09/08/nodejs_api_addon_3/
再续前文,前文介绍了node.js 的addon用法和google v8 引擎,下面,我们进入真正的编码,下面将会通过六个例子,学习node addon
范例,了解addon编程的特性
- 创建一个空项目
- 随机数模块
- 向模块传递参数
- 回调函数处理
- 线程处理
- 对象管理
创建一个空项目
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()
,rand()是C语言编写的函数,产生的数值是C语言类型的,如果node模块需要调用,必须转换格式,Integer::New()
% 2)
整形转换。最后,函数声明的返回类型是Handle
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,
第一参数默认是错误状态,第二个是需要回调的数值,当成功执行的时候,错误状态err设置成null。
status)
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 API系列C/C++ Addons(3) 程序实例,布布扣,bubuko.com