Addons

Addons是动态链接共享对象。它们能提供和c及c++库的粘合。API(当前)是比较复杂的,包含了一些库的知识:
●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

猜你喜欢

转载自flxchy4.iteye.com/blog/1749837