Node.js C/C++ 插件

C/C++ 插件

Node.js Addons(插件)是动态链接的共享对象。他提供了C/C++类库能力。这些API比较复杂,他包以下几个类库:

  • V8 JavaScript, C++类库。用来和JavaScript交互,比如创建对象,调用函数等等。在v8.h头文件中 (目录地址deps/v8/include/v8.h),线上地址online。

Node已经将所有的依赖编译成可以执行文件,所以你不必当心这些类库的链接问题。

以下所有例子可以在download下载,也许你可以从中找一个作为你的扩展插件。

Hello world

现在我们来写一个C++插件的小例子,它的效果和以下JS代码一致:

module.exports.hello = function() { return 'world'; };

通过以下代码来创建hello.cc文件:

// hello.cc #include <node.h> using namespace v8; void Method(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world") } void init(Handle<Object> exports) { NODE_SET_METHOD(exports, "hello", Method } NODE_MODULE(addon, init)

注意:所有的Node插件必须输出一个初始化函数:

void Initialize (Handle<Object> exports NODE_MODULE(module_name, Initialize)

NODE_MODULE之后的代码没有分号,因为它不是一个函数 (参见node.h)。

module_name必须和二进制文件名字一致 (后缀是.node)。

源文件会编译成addon.node二进制插件。为此我们创建了一个很像JSON的binding.gyp文件,它包含配置信息,这个文件用node-gyp编译。

{ "targets": [ { "target_name": "addon", "sources": [ "hello.cc" ] } ] }

下一步创建一个node-gyp configure工程,在平台上生成这些文件。

创建后,在build/文件夹里拥有一个Makefile (Unix系统) 文件或者vcxproj文件(Windows 系统)。接着调用node-gyp build命令编译,生成.node文件。这些文件位于build/Release/目录里。

现在,你能在Node工程中使用这些二进制扩展插件,在hello.js中声明require之前编译的hello.node:

// hello.js var addon = require('./build/Release/addon' console.log(addon.hello() // 'world'

更多的信息请参考https://github.com/arturadib/node-qt

插件模式

下面是一些addon插件的模式,帮助你开始编码。v8 reference文档里包含v8的各种接口,Embedder's Guide这个文档包含各种说明,比如handles, scopes, function templates等等。

在使用这些例子前,你需要先用node-gyp编译。创建binding.gyp 文件:

{ "targets": [ { "target_name": "addon", "sources": [ "addon.cc" ] } ] }

将文件名加入到sources数组里就可以使用多个.cc文件,例如 :

"sources": ["addon.cc", "myexample.cc"]

准备好binding.gyp文件后, 你就能配置并编译插件:

$ node-gyp configure build

函数参数

从以下模式中解释了如何从JavaScript函数中读取参数,并返回结果。仅需要一个addon.cc文件:

// addon.cc #include <node.h> using namespace v8; void Add(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate if (args.Length() < 2) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong number of arguments")) return; } if (!args[0]->IsNumber() || !args[1]->IsNumber()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong arguments")) return; } double value = args[0]->NumberValue() + args[1]->NumberValue( Local<Number> num = Number::New(isolate, value args.GetReturnValue().Set(num } void Init(Handle<Object> exports) { NODE_SET_METHOD(exports, "add", Add } NODE_MODULE(addon, Init)

可以用以下的JavaScript代码片段测试:

// test.js var addon = require('./build/Release/addon' console.log( 'This should be eight:', addon.add(3,5)

回调Callbacks

你也能传JavaScript函数给C++函数,并执行它。在addon.cc中:

// addon.cc #include <node.h> using namespace v8; void RunCallback(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate Local<Function> cb = Local<Function>::Cast(args[0] const unsigned argc = 1; Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") }; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv } void Init(Handle<Object> exports, Handle<Object> module) { NODE_SET_METHOD(module, "exports", RunCallback } NODE_MODULE(addon, Init)

注意,这个例子中使用了Init()里的2个参数,module对象是第二个参数。它允许addon使用一个函数完全重写exports

可以用以下的代码来测试:

// test.js var addon = require('./build/Release/addon' addon(function(msg){ console.log(msg // 'hello world' }

对象工厂

addon.cc模式里,你能用C++函数创建并返回一个新的对象,这个对象所包含的msg属性是由createObject()函数传入:

// addon.cc #include <node.h> using namespace v8; void CreateObject(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate Local<Object> obj = Object::New(isolate obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString() args.GetReturnValue().Set(obj } void Init(Handle<Object> exports, Handle<Object> module) { NODE_SET_METHOD(module, "exports", CreateObject } NODE_MODULE(addon, Init)

使用JavaScript测试:

// test.js var addon = require('./build/Release/addon' var obj1 = addon('hello' var obj2 = addon('world' console.log(obj1.msg+' '+obj2.msg // 'hello world'

工厂模式

这个模式里展示了如何创建并返回一个JavaScript函数,它是由C++函数包装的 :

// addon.cc #include <node.h> using namespace v8; void MyFunction(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world") } void CreateFunction(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction Local<Function> fn = tpl->GetFunction( // omit this to make it anonymous fn->SetName(String::NewFromUtf8(isolate, "theFunction") args.GetReturnValue().Set(fn } void Init(Handle<Object> exports, Handle<Object> module) { NODE_SET_METHOD(module, "exports", CreateFunction } NODE_MODULE(addon, Init)

测试:

// test.js var addon = require('./build/Release/addon' var fn = addon( console.log(fn() // 'hello world'

包装C++对象

以下会创建一个C++对象的包装MyObject,这样他就能在JavaScript中用new实例化。首先在addon.cc中准备主要模块:

// addon.cc #include <node.h> #include "myobject.h" using namespace v8; void InitAll(Handle<Object> exports) { MyObject::Init(exports } NODE_MODULE(addon, InitAll)

接着在myobject.h创建包装,它继承自node::ObjectWrap:

// myobject.h #ifndef MYOBJECT_H #define MYOBJECT_H #include <node.h> #include <node_object_wrap.h> class MyObject : public node::ObjectWrap { public: static void Init(v8::Handle<v8::Object> exports private: explicit MyObject(double value = 0 ~MyObject( static void New(const v8::FunctionCallbackInfo<v8::Value>& args static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args static v8::Persistent<v8::Function> constructor; double value_; }; #endif

myobject.cc中实现各种暴露的方法,通过给构造函数添加prototype属性来暴露plusOne方法:

// myobject.cc #include "myobject.h" using namespace v8; Persistent<Function> MyObject::constructor; MyObject::MyObject(double value) : value_(value) { } MyObject::~MyObject() { } void MyObject::Init(Handle<Object> exports) { Isolate* isolate = Isolate::GetCurrent( // Prepare constructor template Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject") tpl->InstanceTemplate()->SetInternalFieldCount(1 // Prototype NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne constructor.Reset(isolate, tpl->GetFunction() exports->Set(String::NewFromUtf8(isolate, "MyObject"), tpl->GetFunction() } void MyObject::New(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate 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() args.GetReturnValue().Set(args.This() } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor args.GetReturnValue().Set(cons->NewInstance(argc, argv) } } void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder() obj->value_ += 1; args.GetReturnValue().Set(Number::New(isolate, obj->value_) }

测试:

// test.js 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

包装对象工厂

当你想创建本地对象,又不想在JavaScript中严格的使用new初始化的时候,以下方法非常实用:

var obj = addon.createObject( // instead of: // var obj = new addon.Object(

addon.cc中注册createObject方法:

// addon.cc #include <node.h> #include "myobject.h" using namespace v8; void CreateObject(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate MyObject::NewInstance(args } void InitAll(Handle<Object> exports, Handle<Object> module) { MyObject::Init( NODE_SET_METHOD(module, "exports", CreateObject } NODE_MODULE(addon, InitAll)

myobject.h中有静态方法NewInstance,他能实例化对象(它就像JavaScript的new):

// myobject.h #ifndef MYOBJECT_H #define MYOBJECT_H #include <node.h> #include <node_object_wrap.h> class MyObject : public node::ObjectWrap { public: static void Init( static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args private: explicit MyObject(double value = 0 ~MyObject( static void New(const v8::FunctionCallbackInfo<v8::Value>& args static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args static v8::Persistent<v8::Function> constructor; double value_; }; #endif

这个实现方法和myobject.cc类似:

// myobject.cc #include <node.h> #include "myobject.h" using namespace v8; Persistent<Function> MyObject::constructor; MyObject::MyObject(double value) : value_(value) { } MyObject::~MyObject() { } void MyObject::Init() { Isolate* isolate = Isolate::GetCurrent( // Prepare constructor template Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject") tpl->InstanceTemplate()->SetInternalFieldCount(1 // Prototype NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne constructor.Reset(isolate, tpl->GetFunction() } void MyObject::New(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate 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() args.GetReturnValue().Set(args.This() } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor args.GetReturnValue().Set(cons->NewInstance(argc, argv) } } void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate const unsigned argc = 1; Handle<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor Local<Object> instance = cons->NewInstance(argc, argv args.GetReturnValue().Set(instance } void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder() obj->value_ += 1; args.GetReturnValue().Set(Number::New(isolate, obj->value_) }

测试:

// test.js var createObject = require('./build/Release/addon' var obj = createObject(10 console.log( obj.plusOne() // 11 console.log( obj.plusOne() // 12 console.log( obj.plusOne() // 13 var obj2 = createObject(20 console.log( obj2.plusOne() // 21 console.log( obj2.plusOne() // 22 console.log( obj2.plusOne() // 23

传递包装对象

除了包装并返回C++对象,你可以使用Node的node::ObjectWrap::Unwrap帮助函数来解包。在下面的addon.cc中,我们介绍了一个add()函数,它能获取2个 MyObject对象:

// addon.cc #include <node.h> #include <node_object_wrap.h> #include "myobject.h" using namespace v8; void CreateObject(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate MyObject::NewInstance(args } void Add(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>( args[0]->ToObject() MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>( args[1]->ToObject() double sum = obj1->value() + obj2->value( args.GetReturnValue().Set(Number::New(isolate, sum) } void InitAll(Handle<Object> exports) { MyObject::Init( NODE_SET_METHOD(exports, "createObject", CreateObject NODE_SET_METHOD(exports, "add", Add } NODE_MODULE(addon, InitAll)

介绍myobject.h里的一个公开方法,它能在解包后使用私有变量:

// myobject.h #ifndef MYOBJECT_H #define MYOBJECT_H #include <node.h> #include <node_object_wrap.h> class MyObject : public node::ObjectWrap { public: static void Init( static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args inline double value() const { return value_; } private: explicit MyObject(double value = 0 ~MyObject( static void New(const v8::FunctionCallbackInfo<v8::Value>& args static v8::Persistent<v8::Function> constructor; double value_; }; #endif

myobject.cc的实现方法和之前的类似:

// myobject.cc #include <node.h> #include "myobject.h" using namespace v8; Persistent<Function> MyObject::constructor; MyObject::MyObject(double value) : value_(value) { } MyObject::~MyObject() { } void MyObject::Init() { Isolate* isolate = Isolate::GetCurrent( // Prepare constructor template Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject") tpl->InstanceTemplate()->SetInternalFieldCount(1 constructor.Reset(isolate, tpl->GetFunction() } void MyObject::New(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate 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() args.GetReturnValue().Set(args.This() } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor args.GetReturnValue().Set(cons->NewInstance(argc, argv) } } void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent( HandleScope scope(isolate const unsigned argc = 1; Handle<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor Local<Object> instance = cons->NewInstance(argc, argv args.GetReturnValue().Set(instance }

测试:

// test.js var addon = require('./build/Release/addon' var obj1 = addon.createObject(10 var obj2 = addon.createObject(20 var result = addon.add(obj1, obj2 console.log(result // 30