●V8 JavaScript,一个C++库。被用来作为Javascript接口:建立对象,调用方法等等。在v8.h头文件中有说明文档(在Node源树中的deps/v8/include/v8.h),它也可在线查看。
●libuv,C事件loop库。任何时候谁需要等待一个文件描述符变成可读,等待一个计时器,或者等待一个信号来接收什么将需要和libuv接口。换言之,如果你执行任何I/O,libuv将需要被使用到。
●内部Node库。Most importantly is the node::ObjectWrap class which you will likely want to derive from.
●其他。 Look in deps/ for what else is available.
Node静态编译它的所有依赖到可执行文件。当编译你的模块,你不需要担心链接到任何这些库。
Hello world
让我们建立一简单的Addon,它是一个C++,等价于下面的Javascript代码:
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> target) { target->Set(String::NewSymbol("hello"), FunctionTemplate::New(Method)->GetFunction()); } NODE_MODULE(hello, init)
注意所有的Node addons必须导出一个初始化函数:
void Initialize (Handle<Object> target); NODE_MODULE(module_name, Initialize)
在NODE_MODULE后没有分号因为它不是一个函数(请看node.h)。
module_name 需要对应最后的二进制文件名(去掉node后缀)。
源代码需要构建到hello.node中,二进制Addon。为了做到这个我们建立一个名为binding.gyp文件,它以类似JSON的格式描述了建立你的模块的配置。这个文件通过node-gyp编译。
{ "targets": [ { "target_name": "hello", "sources": [ "hello.cc" ] } ] }
下一步是为当前平台生成适当的项目构建文件。为此使用node-gypconfigure。
现在你将会在build/ directory目录下有一个Makefile(在Unix平台上)或者vcxproj文件(在Windows平台上)。下一步调用node-gyp build命令。
现在你有你的编译过的.node bindings文件了!编译过的bindings在build/Release/里。
你现在能在Node项目hello.js中使用字节addon了,通过将require指向刚建立的hello.node模块:
var addon = require('./build/Release/hello'); console.log(addon.hello()); // 'world'
更多的信息请参看下面的模式或https://github.com/arturadib/node-qt 中一个产品项目。
Addon 模式
下面是一些addon模式来帮助你开始。请参考在线v8参考帮助中的各种v8调用,和v8的Embedder指南对使用的几个概念的解释,比如handles,scopes,function templates等待。
为了去使用这些示例你需要使用node-gyp编译它们。建立如下的binding.gyp文件:
{ "targets": [ { "target_name": "addon", "sources": [ "addon.cc" ] } ] }
在某些情况下有不止一个.cc文件,只需添加文件的名字到来源数组,例如:
"sources": ["addon.cc", "myexample.cc"]
现在你的binding.gyp准备就绪,你能配置和组件addon:
$ node-gyp configure build
函数参数
以下模式演示了如何从JavaScript函数调用读取参数并返回一个结果。这是主要的,和唯一必要的源代码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> target) { target->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) );
回调
你能传递Javascript函数到C++函数中并在那里执行它们。下面是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> target) { target->Set(String::NewSymbol("runCallback"), FunctionTemplate::New(RunCallback)->GetFunction()); } NODE_MODULE(addon, Init)
使用下面的Javascript片段进行测试:
var addon = require('./build/Release/addon'); addon.runCallback(function(msg){ console.log(msg); // 'hello world' });
对象工厂
通过这个addon.cc模式,你能在C++函数中建立和返回新对象,它返回的新对象有msg属性,它echo传递到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> target) { target->Set(String::NewSymbol("createObject"), FunctionTemplate::New(CreateObject)->GetFunction()); } NODE_MODULE(addon, Init)
在Javascript中进行测试:
var addon = require('./build/Release/addon'); var obj1 = addon.createObject('hello'); var obj2 = addon.createObject('world'); console.log(obj1.msg+' '+obj2.msg); // 'hello world'
函数工厂
这种模式演示了如何创建并返回一个包装了一个c++函数的JavaScript函数:
#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> target) { target->Set(String::NewSymbol("createFunction"), FunctionTemplate::New(CreateFunction)->GetFunction()); } NODE_MODULE(addon, Init)
如下测试:
var addon = require('./build/Release/addon'); var fn = addon.createFunction(); console.log(fn()); // 'hello world'
Wrapping C++对象
这里我们将为 C++ object/class MyObject 建立一个wrapper,该object可以通过新的操作符在JavaScript中实例化。首先准备主要的模块addon.css:
#define BUILDING_NODE_EXTENSION #include <node.h> #include "myobject.h" using namespace v8; void InitAll(Handle<Object> target) { MyObject::Init(target); } 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> target); private: MyObject(); ~MyObject(); static v8::Handle<v8::Value> New(const v8::Arguments& args); static v8::Handle<v8::Value> PlusOne(const v8::Arguments& args); double counter_; }; #endif
然后在myobject.cc中实现你想要公开的各种方法。这里我们公开方法plugOne,则添加它到constructor's prototype:
#define BUILDING_NODE_EXTENSION #include <node.h> #include "myobject.h" using namespace v8; MyObject::MyObject() {}; MyObject::~MyObject() {}; void MyObject::Init(Handle<Object> target) { // 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()); Persistent<Function> constructor = Persistent<Function>::New(tpl->GetFunction()); target->Set(String::NewSymbol("MyObject"), constructor); } Handle<Value> MyObject::New(const Arguments& args) { HandleScope scope; MyObject* obj = new MyObject(); obj->counter_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); obj->Wrap(args.This()); return args.This(); } Handle<Value> MyObject::PlusOne(const Arguments& args) { HandleScope scope; MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This()); obj->counter_ += 1; return scope.Close(Number::New(obj->counter_)); }
如下测试:
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
Factory of wrapped objects
这是有用的,例如当你希望能够创建本地对象又不在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> target) { MyObject::Init(); target->Set(String::NewSymbol("createObject"), 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: MyObject(); ~MyObject(); static v8::Persistent<v8::Function> constructor; static v8::Handle<v8::Value> New(const v8::Arguments& args); static v8::Handle<v8::Value> PlusOne(const v8::Arguments& args); double counter_; }; #endif
The implementation is similar to the above in myobject.cc:
#define BUILDING_NODE_EXTENSION #include <node.h> #include "myobject.h" using namespace v8; MyObject::MyObject() {}; MyObject::~MyObject() {}; Persistent<Function> MyObject::constructor; 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; MyObject* obj = new MyObject(); obj->counter_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); obj->Wrap(args.This()); return args.This(); } 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->counter_ += 1; return scope.Close(Number::New(obj->counter_)); }
如下测试:
var addon = require('./build/Release/addon'); var obj = addon.createObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 var obj2 = addon.createObject(20); console.log( obj2.plusOne() ); // 21 console.log( obj2.plusOne() ); // 22 console.log( obj2.plusOne() ); // 23
Passing wrapped objects around
除了包装并返回c++对象,你能通过使用Node's node::ObjectWrap::Unwrap helper function展开它们来pass them around。在下面的addon.cc我们引入一个函数add(),它可以接纳两个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->Val() + obj2->Val(); return scope.Close(Number::New(sum)); } void InitAll(Handle<Object> target) { MyObject::Init(); target->Set(String::NewSymbol("createObject"), FunctionTemplate::New(CreateObject)->GetFunction()); target->Set(String::NewSymbol("add"), FunctionTemplate::New(Add)->GetFunction()); } NODE_MODULE(addon, InitAll)
为了使事情有趣的我们在myobject.h中引入一个公共方法,这样我们就可以在对象展开后探测私有值:
#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 Val() const { return val_; } private: MyObject(); ~MyObject(); static v8::Persistent<v8::Function> constructor; static v8::Handle<v8::Value> New(const v8::Arguments& args); double val_; }; #endif
myobject.cc的实现和前面类似:
#define BUILDING_NODE_EXTENSION #include <node.h> #include "myobject.h" using namespace v8; MyObject::MyObject() {}; MyObject::~MyObject() {}; Persistent<Function> MyObject::constructor; 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; MyObject* obj = new MyObject(); obj->val_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); obj->Wrap(args.This()); return args.This(); } 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