Addons插件就是动态连接库。Addons插件将Node与C库和C++库链接起来。它的API(目前来说)相当复杂,涉及到了几个类库的知识。
v8.h
头文件 (deps/v8/include/v8.h
在Node源代码目录里), 也有可用的线上文档线上。node::ObjectWrap
类,这个类你应该是最可能想要派生的。deps/
获得更多可用类库。Node 静态编译了所有依赖到它的可执行文件中去了。当编译你的模块时,你不必担心无法连接上述那些类库。
下面所有的例子都可以下载到:下载 这或许能成为你学习和创作自己addon插件的起点。
作为开始,让我们用编写一个小的addon插件,这个addon插件的c++代码相当于下面的JavaScript代码。
module.exports.hello = function() { return 'world'; };
首先我们创建一个
hello.cc
文件:
#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插件都必须输出一个初始化函数:
void Initialize (Handle<Object> exports);
NODE_MODULE(module_name, Initialize)
在
NODE_MODULE
之后没有分号,因为它不是一个函数(请参阅
node.h
)
这个module_name
(模块名)需要和最后编译生成的2进制文件名(减去.node后缀名)相同。
源代码需要生成在hello.node
,这个2进制addon插件中。需要做到这些,我们要创建一个名为binding.gyp
的文件,它描述了创建这个模块的配置,并且它的格式是类似JSON的。文件将被命令:node-gyp 编译。
{
"targets": [
{
"target_name": "hello",
"sources": [ "hello.cc" ]
}
]
}
下一步是根据当前的操作系统平台,利用
node-gyp configure
命令,生成合适的项目文件。
现在你会有一个Makefile
(在Unix平台) 或者一个 vcxproj
file(在Windows上),它们都在build/
文件夹中. 然后执行命令 node-gyp build
进行编译。
现在你已经有了编译好的 .node
文件了,这个编译好的绑定文件会在目录 build/Release/
下
hello.js
中了,通过指明
require
这个刚刚创建的
hello.node
模块使用它。
var addon = require('./build/Release/hello');
console.log(addon.hello()); // 'world'
请阅读下面的内容获得更多详情或者访问
https://github.com/arturadib/node-qt获取一个生产环境的例子。
为了能跑起来这些例子,你必须用 node-gyp
来编译他们。创建一个binding.gyp
文件:
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ]
}
]
}
事实上可以有多个
.cc
文件, 就简单的在
sources
数组里加上即可,示例:
"sources": ["addon.cc", "myexample.cc"]
现在你有了你的binding.gyp
文件了,你可要开始执行configure 和 build 命令构建你的addon插件了
$ node-gyp configure build
addon.cc
。
#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)
你可以使用下面的JavaScript代码来测试它
var addon = require('./build/Release/addon');
console.log( 'This should be eight:', addon.add(3,5) );
addon.cc
文件:
#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)
注意这个例子对
Init()
使用了两个参数,将完整的
module
对象作为第二个参数传入。这允许addon插件完全的重写
exports
,这样就可以用一个函数代替多个函数作为
exports
的属性了。
你可以使用下面的JavaScript代码来测试它:
var addon = require('./build/Release/addon');
addon(function(msg){
console.log(msg); // 'hello world'
});
在这个addon.cc
文件里用一个c++函数,你可以创建并且返回一个新的对象,这个新的对象拥有一个msg的属性,它的值是通过createObject()方法传入的
#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)
在js中测试如下:
var addon = require('./build/Release/addon');
var obj1 = addon('hello');
var obj2 = addon('world');
console.log(obj1.msg+' '+obj2.msg); // 'hello world'
#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)
测试:
var addon = require('./build/Release/addon');
var fn = addon();
console.log(fn()); // 'hello world'
MyObject
,它是可以在JavaScript中通过
new
操作符实例化的。首先我们要准备主要的模块文件
addon.cc
:
#define BUILDING_NODE_EXTENSION
#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
的包装类
:
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <node.h>
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_;
};
#endif
在文件 myobject.cc
可以实现各种你想要显示的方法。 这里我们通过将方法添加到构造函数的原型中来显示方法名为 plusOne的函数。
#define BUILDING_NODE_EXTENSION
#include <node.h>
#include "myobject.h"
using namespace v8;
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_));
}
使用以下示例测试:
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对象,又不想明确的使用JavaScript的new
操作符时,这种模式是非常有用的。
var obj = addon.createObject();
// instead of:
// var obj = new addon.Object();
让我们注册在 addon.cc
文件中注册createObject
方法:
#define BUILDING_NODE_EXTENSION
#include <node.h>
#include "myobject.h"
using namespace v8;
Handle<Value> CreateObject(const Arguments& args) {
HandleScope scope;
return scope.Close(MyObject::NewInstance(args));
}
void InitAll(Handle<Object> exports, Handle<Object> module) {
MyObject::Init();
module->Set(String::NewSymbol("exports"),
FunctionTemplate::New(CreateObject)->GetFunction());
}
NODE_MODULE(addon, InitAll)
在
myobject.h
文件中,我们现在引入了
能够实例化对象
的静态方法NewInstance
(它的工作就像是 在JavaScript中的
new操作符。)
#define BUILDING_NODE_EXTENSION
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <node.h>
class MyObject : public node::ObjectWrap {
public:
static void Init();
static v8::Handle<v8::Value> NewInstance(const v8::Arguments& args);
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_;
};
#endif
这里的实现模式与上面的
myobject.cc
类似:
#define BUILDING_NODE_EXTENSION
#include <node.h>
#include "myobject.h"
using namespace v8;
Persistent<Function> MyObject::constructor;
MyObject::MyObject(double value) : value_(value) {
}
MyObject::~MyObject() {
}
void MyObject::Init() {
// 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());
}
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::NewInstance(const Arguments& args) {
HandleScope scope;
const unsigned argc = 1;
Handle<Value> argv[argc] = { args[0] };
Local<Object> instance = constructor->NewInstance(argc, argv);
return scope.Close(instance);
}
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_));
}
测试:
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
node::ObjectWrap::Unwrap
帮助函数解包装他们。在下面的
addon.cc
文件中,我们介绍了一个函数
add()
,它能够获取2个
MyObject
对象。
#define BUILDING_NODE_EXTENSION
#include <node.h>
#include "myobject.h"
using namespace v8;
Handle<Value> CreateObject(const Arguments& args) {
HandleScope scope;
return scope.Close(MyObject::NewInstance(args));
}
Handle<Value> Add(const Arguments& args) {
HandleScope scope;
MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
args[0]->ToObject());
MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
args[1]->ToObject());
double sum = obj1->Value() + obj2->Value();
return scope.Close(Number::New(sum));
}
void InitAll(Handle<Object> exports) {
MyObject::Init();
exports->Set(String::NewSymbol("createObject"),
FunctionTemplate::New(CreateObject)->GetFunction());
exports->Set(String::NewSymbol("add"),
FunctionTemplate::New(Add)->GetFunction());
}
NODE_MODULE(addon, InitAll)
为了使事情变得有趣,我们在
myobject.h
采用一个公共的方法,所以我们能够在unwrapping解包装对象之后使用私有成员的值。
#define BUILDING_NODE_EXTENSION
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <node.h>
class MyObject : public node::ObjectWrap {
public:
static void Init();
static v8::Handle<v8::Value> NewInstance(const v8::Arguments& args);
double Value() const { return value_; }
private:
explicit MyObject(double value = 0);
~MyObject();
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Persistent<v8::Function> constructor;
double value_;
};
#endif
myobject.cc
文件的实现模式前面类似:
#define BUILDING_NODE_EXTENSION
#include <node.h>
#include "myobject.h"
using namespace v8;
Persistent<Function> MyObject::constructor;
MyObject::MyObject(double value) : value_(value) {
}
MyObject::~MyObject() {
}
void MyObject::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
tpl->SetClassName(String::NewSymbol("MyObject"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
constructor = Persistent<Function>::New(tpl->GetFunction());
}
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::NewInstance(const Arguments& args) {
HandleScope scope;
const unsigned argc = 1;
Handle<Value> argv[argc] = { args[0] };
Local<Object> instance = constructor->NewInstance(argc, argv);
return scope.Close(instance);
}
测试:
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