Nodejs的Addons就是动态链接库(C/C++),主要涉及的有
-
V8 Javascript, a C++ library
- 主要用于实现Javascript的接口:creating objects, calling functions…
-
libuv,C实现的event loop library
- 当需要执行阻塞/异步操作时,需要使用libuv,例如:等待文件就绪、等待定时器超时、等待signal…
-
internal Node libraries
- node::Objectwrap
- …
-
others, see deps/
一个简单的实例
实现功能如下的模块
module.exports.hello = function() { return 'world'; };
C++代码如下
#include <node.h>
#include <v8.h>
using namespace v8;
Handle<Value> Method(const Arguments& args) {
HandleScope scope;
return scope.Close(String::New("world"));
}
void init(Handle<Object> exports) {
exports->Set(String::NewSymbol("hello"),
FunctionTemplate::New(Method)->GetFunction());
}
NODE_MODULE(hello, init)
所有的Node Addons必须实现一个初始化函数,由 NODE_MODULE指定:
NODE_MODULE(module_name, Initialize),module_name必须和最终库文件的文件名前缀匹配(module_name.node)
C++文件由node-gyp工具进行编译,配置文件
{
"targets": [
{
"target_name": "hello",
"sources": [ "hello.cc" ]
}
]
}
运行命令
node-gyp configure
node-gyp build
测试代码
var addon = require('./build/Release/hello');
console.log(addon.hello()); // 'world'
函数参数
测试代码
var addon = require('./build/Release/addon');
console.log( 'This should be eight:', addon.add(3,5) );
需要在C++代码中处理数字参数
#define BUILDING_NODE_EXTENSION
#include <node.h>
using namespace v8;
Handle<Value> Add(const Arguments& args) {
HandleScope scope;
if (args.Length() < 2) {
ThrowException(Exception::TypeError(String::New("Wrong number of arguments")));
return scope.Close(Undefined());
}
if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
ThrowException(Exception::TypeError(String::New("Wrong arguments")));
return scope.Close(Undefined());
}
Local<Number> num = Number::New(args[0]->NumberValue() +
args[1]->NumberValue());
return scope.Close(num);
}
void Init(Handle<Object> exports) {
exports->Set(String::NewSymbol("add"),
FunctionTemplate::New(Add)->GetFunction());
}
NODE_MODULE(addon, Init)
回调函数
回到函数也是通过参数进行传递
var addon = require('./build/Release/addon');
addon(function(msg){
console.log(msg); // 'hello world'
});
C++代码
#define BUILDING_NODE_EXTENSION
#include <node.h>
using namespace v8;
Handle<Value> RunCallback(const Arguments& args) {
HandleScope scope;
Local<Function> cb = Local<Function>::Cast(args[0]);
const unsigned argc = 1;
Local<Value> argv[argc] = { Local<Value>::New(String::New("hello world")) };
cb->Call(Context::GetCurrent()->Global(), argc, argv);
return scope.Close(Undefined());
}
void Init(Handle<Object> exports, Handle<Object> module) {
module->Set(String::NewSymbol("exports"),
FunctionTemplate::New(RunCallback)->GetFunction());
}
NODE_MODULE(addon, Init)
对象工厂/Object Factory
函数返回一个对象
var addon = require('./build/Release/addon');
var obj1 = addon('hello');
var obj2 = addon('world');
console.log(obj1.msg+' '+obj2.msg); // 'hello world'
C++代码
#define BUILDING_NODE_EXTENSION
#include <node.h>
using namespace v8;
Handle<Value> CreateObject(const Arguments& args) {
HandleScope scope;
Local<Object> obj = Object::New();
obj->Set(String::NewSymbol("msg"), args[0]->ToString());
return scope.Close(obj);
}
void Init(Handle<Object> exports, Handle<Object> module) {
module->Set(String::NewSymbol("exports"),
FunctionTemplate::New(CreateObject)->GetFunction());
}
NODE_MODULE(addon, Init)
与上述例子不同的是,Init函数接收第二个参数module,通过module将函数导出
类似,可以使函数返回一个函数
var addon = require('./build/Release/addon');
var fn = addon();
console.log(fn()); // 'hello world'
C++代码
#define BUILDING_NODE_EXTENSION
#include <node.h>
using namespace v8;
Handle<Value> MyFunction(const Arguments& args) {
HandleScope scope;
return scope.Close(String::New("hello world"));
}
Handle<Value> CreateFunction(const Arguments& args) {
HandleScope scope;
Local<FunctionTemplate> tpl = FunctionTemplate::New(MyFunction);
Local<Function> fn = tpl->GetFunction();
fn->SetName(String::NewSymbol("theFunction")); // omit this to make it anonymous
return scope.Close(fn); //区别在此
}
void Init(Handle<Object> exports, Handle<Object> module) {
module->Set(String::NewSymbol("exports"),
FunctionTemplate::New(CreateFunction)->GetFunction());
}
NODE_MODULE(addon, Init)
包装C++对象/Wrapping C++ objects
通过此方法,可以在Javascript中实例化C++类
#include <node.h>
using namespace v8;
class MyObject : public node::ObjectWrap {
public:
static void Init(v8::Handle<v8::Object> exports);
private:
explicit MyObject(double value = 0);
~MyObject();
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> PlusOne(const v8::Arguments& args);
static v8::Persistent<v8::Function> constructor;
double value_;
};
Persistent<Function> MyObject::constructor;
MyObject::MyObject(double value) : value_(value) {
}
MyObject::~MyObject() {
}
void MyObject::Init(Handle<Object> exports) {
// Prepare constructor template
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
tpl->SetClassName(String::NewSymbol("MyObject"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
// Prototype
tpl->PrototypeTemplate()->Set(String::NewSymbol("plusOne"),
FunctionTemplate::New(PlusOne)->GetFunction());
constructor = Persistent<Function>::New(tpl->GetFunction());
exports->Set(String::NewSymbol("MyObject"), constructor);
}
Handle<Value> MyObject::New(const Arguments& args) {
HandleScope scope;
if (args.IsConstructCall()) {
// Invoked as constructor: `new MyObject(...)`
double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
MyObject* obj = new MyObject(value);
obj->Wrap(args.This());
return args.This();
} else {
// Invoked as plain function `MyObject(...)`, turn into construct call.
const int argc = 1;
Local<Value> argv[argc] = { args[0] };
return scope.Close(constructor->NewInstance(argc, argv));
}
}
Handle<Value> MyObject::PlusOne(const Arguments& args) {
HandleScope scope;
MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This());
obj->value_ += 1;
return scope.Close(Number::New(obj->value_));
}
void InitAll(Handle<Object> exports) {
MyObject::Init(exports);
}
NODE_MODULE(addon, InitAll)
重点在于FunctionTemplate、ObjectWrap
测试代码
var addon = require('./build/Release/addon');
var obj = new addon.MyObject(10);
console.log( obj.plusOne() ); // 11
console.log( obj.plusOne() ); // 12
console.log( obj.plusOne() ); // 13
可以看出,Node Addons的基本任务在于C++和Javascript中参数的传递/转换,主要涉及V8、Node library。至于Addon的功能实现,都将和libuv紧密相关(所有的函数都应做到nonblocking)。