Node.js Addons(插件)是動態(tài)鏈接的共享對象。他提供了C/C++類庫能力。這些API比較復(fù)雜,他包以下幾個類庫:
V8 JavaScript, C++類庫。用來和JavaScript交互,比如創(chuàng)建對象,調(diào)用函數(shù)等等。在v8.h
頭文件中 (目錄地址deps/v8/include/v8.h
),線上地址online。
libuv,C事件循環(huán)庫。等待文件描述符變?yōu)榭勺x,等待定時器,等待信號時,會和libuv打交道。或者說,如果你需要和I/O打交道,就會用到libuv。
內(nèi)部Node類庫。其中最重要的類node::ObjectWrap
,你會經(jīng)常派生自它。
deps/
。Node已經(jīng)將所有的依賴編譯成可以執(zhí)行文件,所以你不必?fù)?dān)心這些類庫的鏈接問題。
以下所有例子可以在download下載,也許你可以從中找一個作為你的擴(kuò)展插件。
現(xiàn)在我們來寫一個C++插件的小例子,它的效果和以下JS代碼一致:
module.exports.hello = function() { return 'world'; };
通過以下代碼來創(chuàng)建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插件必須輸出一個初始化函數(shù):
void Initialize (Handle<Object> exports);
NODE_MODULE(module_name, Initialize)
NODE_MODULE
之后的代碼沒有分號,因為它不是一個函數(shù) (參見node.h
)。
module_name
必須和二進(jìn)制文件名字一致 (后綴是.node)。
源文件會編譯成addon.node
二進(jìn)制插件。為此我們創(chuàng)建了一個很像JSON的binding.gyp
文件,它包含配置信息,這個文件用node-gyp編譯。
{
"targets": [
{
"target_name": "addon",
"sources": [ "hello.cc" ]
}
]
}
下一步創(chuàng)建一個node-gyp configure
工程,在平臺上生成這些文件。
創(chuàng)建后,在build/
文件夾里擁有一個Makefile
(Unix系統(tǒng)) 文件或者vcxproj
文件(Windows 系統(tǒng))。接著調(diào)用node-gyp build
命令編譯,生成.node
文件。這些文件位于build/Release/
目錄里。
現(xiàn)在,你能在Node工程中使用這些二進(jìn)制擴(kuò)展插件,在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
編譯。創(chuàng)建binding.gyp
文件:
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ]
}
]
}
將文件名加入到sources
數(shù)組里就可以使用多個.cc
文件,例如 :
"sources": ["addon.cc", "myexample.cc"]
準(zhǔn)備好binding.gyp
文件后, 你就能配置并編譯插件:
$ node-gyp configure build
從以下模式中解釋了如何從JavaScript函數(shù)中讀取參數(shù),并返回結(jié)果。僅需要一個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) );
你也能傳JavaScript函數(shù)給C++函數(shù),并執(zhí)行它。在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個參數(shù),module
對象是第二個參數(shù)。它允許addon使用一個函數(shù)完全重寫exports
。
可以用以下的代碼來測試:
// test.js
var addon = require('./build/Release/addon');
addon(function(msg){
console.log(msg); // 'hello world'
});
在addon.cc
模式里,你能用C++函數(shù)創(chuàng)建并返回一個新的對象,這個對象所包含的msg
屬性是由createObject()
函數(shù)傳入:
// 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'
這個模式里展示了如何創(chuàng)建并返回一個JavaScript函數(shù),它是由C++函數(shù)包裝的 :
// 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'
以下會創(chuàng)建一個C++對象的包裝MyObject
,這樣他就能在JavaScript中用new
實例化。首先在addon.cc
中準(zhǔn)備主要模塊:
// 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
創(chuàng)建包裝,它繼承自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
中實現(xiàn)各種暴露的方法,通過給構(gòu)造函數(shù)添加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
當(dāng)你想創(chuàng)建本地對象,又不想在JavaScript中嚴(yán)格的使用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
中有靜態(tài)方法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
這個實現(xiàn)方法和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
幫助函數(shù)來解包。在下面的addon.cc
中,我們介紹了一個add()
函數(shù),它能獲取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
的實現(xiàn)方法和之前的類似:
// 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
如果你想了解更多與C++相關(guān)的知識,則可以參考本站的《C++教程》!
更多建議: