对官方NAN模块的说明进行翻译。
1、用法
只需要简单的在package.json中加入如下语句:
$ npm install ---save nan
在bind.gyp中加入NAN路径,这样才能够在.cpp文件中使用命名空间#include <nan.h>
“include_dirs”:[“<!(node –e \”require(‘nan’)\”)”]
2、例子
将官方版本的例子下载下来,https://github.com/nodejs/node-addon-examples,运行其中的例子,发现一些问题。
(1)helloworld里面一共三种c++引入node的方式(NAN,node0.10,node0.12),其中nan文件夹包含的hello.cc文件引用了<nan.h>,在binding.gyp中配置了文件路径,包括两个函数,Method函数与Init函数,经过编译测试可以用(其中需要npm下载包bindings);nan0.1会报错,莫名其妙的错误(
make: *** [Release/obj.target/hello/hello.o]Error 1
make: Leaving directory`/home/neil/Projects/Nodejs/cps/src/example-nan/1_hello_world/node_0.10/build'
gyp ERR! build error
gyp ERR! stack Error: `make` failed withexit code: 2
gyp ERR! stack at ChildProcess.onExit(/usr/local/lib/node_modules/node-gyp/lib/build.js:276:23)
gyp ERR! stack at emitTwo (events.js:87:13)
gyp ERR! stack at ChildProcess.emit (events.js:172:7)):思考了许久,查阅资料,牛人说大概是因为V8引擎经过了一次大的升级,里面的c++函数库并不能兼容导致的错误Handle<Value> Method这种写法已经不行了,(坑爹啊,这V8跨度如此大,不怕扯着档了么)。
然后继续对node0.12版本进行编译,发现也能通过。仔细对比这三种方式,得出它们的区别如下:
首先,第一种使用NAN包方式实现对c函数的调用,核心部分需要写两个函数,
第一个是Method函数,定义回调方法,第二个是初始化函数Init,将Method函数中的world在js中输出。这个hello world的基本的功能就结束了。
(2)第二个例子中的function arguments同样也分为三种调用方式,这里只使第一个(NAN的调用方式)。与第一部分helloworld不同,这部分给出了如何在c与js之间进行参数定义与传递。具体方法如下:
首先定义一个.cc文件,里面用c进行定义函数,定义空函数。
void Add(const Nan::FunctionCallbackInfo<v8::Value>&info) {//定义函数
if (info.Length()< 2) {//判断传入参数是否满足要求
Nan::ThrowTypeError("Wrong number of arguments");
return;
}
if (!info[0]->IsNumber() || !info[1]->IsNumber()) {
Nan::ThrowTypeError("Wrong arguments");
return;
}
double arg0= info[0]->NumberValue();//取参数
double arg1= info[1]->NumberValue();
v8::Local<v8::Number> num =Nan::New(arg0 + arg1);//计算(注意该处参数类型的写法)
info.GetReturnValue().Set(num);//返回计算结果
}
将该add方法导出:
void Init(v8::Local<v8::Object> exports) {//初始化函数导出add方法 exports->Set(Nan::New("add").ToLocalChecked(),//引号中为导出以后的函数名,在js中调用 Nan::New<v8::FunctionTemplate>(Add)->GetFunction());//括号中为C中定义的函数 } NODE_MODULE(addon, Init)
在binding.gyp里面写:
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ],
"include_dirs": [
"<!(node -e \"require('nan')\")"
]
}
]
}
通过node-gyp configure build命令可以进行编译。
然后在addon.js中队这个add函数进行调用
var addon = require('./build/Release/addon'); console.log('This should be eight:', addon.add(3,1))
得到结果。
可见上面几个文件中,最为关键的是对add函数的定义方式,这里传入参数的写法很特殊,必须以const Nan::FunctionCallbackInfo<v8::Value>& info的形式,返回值也必须是V8的内部格式,经过NAN::new的处理赋值,不然会报错。最后返回不是用return而是利用info.GetReturnValue().Set(num)来完成。
(3)第三个例子是关于回调函数的callback的,进入nan目录,其他方面都和上面两个例子一样,唯一不同的是在addon.cc文件中对回调函数的定义。
#include <nan.h> void RunCallback(const Nan::FunctionCallbackInfo<v8::Value>& info) {//定义方式一样 v8::Local<v8::Function> cb = info[0].As<v8::Function>();//定义第一个参数为回调函数 const unsigned argc = 1; v8::Local<v8::Value> argv[argc] = { Nan::New("hello world").ToLocalChecked() };//定义字符串指针argv Nan::MakeCallback(Nan::GetCurrentContext()->Global(), cb, argc, argv);将cb函数定义为一个字符串指针,并映射到全局,MakeCallback有四个参数,其中第一个是目标,Global()代表映射到全局,二个参数是函数名称,第三个参数为argc,第四个参数为字符串指针或者其他操作。 } void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) {//初始化函数,将回调函数进行映射 Nan::SetMethod(module, "exports", RunCallback);//通过SetMethod函数将RunCallback函数映射到js中的module去,由于此处为回调函数,所以必须用SetMethod而不是第二例子方法使用的V8objects的Set函数。 } NODE_MODULE(addon, Init)
然后编译,在js文件中对回调函数的调用方式如下:
var addon = require('bindings')('addon'); addon(function(msg){ console.log(msg); // 'hello world' });
(4)第4个例子是关于对象工厂的实现,所为对象工厂就是通过c函数构建的对象传递给nodejs调用。在addon.cc文件中有如下代码
#include <nan.h> void CreateObject(const Nan::FunctionCallbackInfo<v8::Value>& info) { v8::Local<v8::Object> obj = Nan::New<v8::Object>();//定义对象 obj->Set(Nan::New("msg").ToLocalChecked(), info[0]->ToString());//将info给对象属性msg赋值 info.GetReturnValue().Set(obj);//返回该对象 } void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) { module->Set(Nan::New("exports").ToLocalChecked(), Nan::New<v8::FunctionTemplate>(CreateObject)->GetFunction());//将创建对象函数CreateObject映射到js中 } NODE_MODULE(addon, Init)
在js端对创建对象函数的调用代码如下:
var addon = require('bindings')('addon');//需要安装bindings包 var obj1 = addon('hello');//将‘hello’参数传递进去,生产obj1对象 var obj2 = addon('world'); console.log(obj1.msg+' '+obj2.msg); // 'hello world'
(5)第5个例子是关于函数工厂的实现,与对象工厂几乎类似,只是将对象变成了函数。
(6)第6个例子是关于对象封装。主要是实现在c语言中对象的传递使用,首先进入头文件myobjects.h:
#ifndef MYOBJECT_H #define MYOBJECT_H #include <nan.h> class MyObject : public Nan::ObjectWrap {//基本的写法,继承 public: static void Init(v8::Local<v8::Object> exports);//必须定义初始化函数 private: explicit MyObject(double value = 0);//构造函数 ~MyObject(); //成员函数,必须定义成静态的 static void New(const Nan::FunctionCallbackInfo<v8::Value>& info); static void GetValue(const Nan::FunctionCallbackInfo<v8::Value>& info); static void PlusOne(const Nan::FunctionCallbackInfo<v8::Value>& info); static void Multiply(const Nan::FunctionCallbackInfo<v8::Value>& info); static Nan::Persistent<v8::Function> constructor; double value_; }; #endif
然后进如myobjects.cpp文件
#include "myobject.h" Nan::Persistent<v8::Function> MyObject::constructor; MyObject::MyObject(double value) : value_(value) { } MyObject::~MyObject() { } void MyObject::Init(v8::Local<v8::Object> exports) {//初始化函数 Nan::HandleScope scope;//定义scope范围 // Prepare constructor template v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);//定义V8函数模板 tpl->SetClassName(Nan::New("MyObject").ToLocalChecked());//定义名称 tpl->InstanceTemplate()->SetInternalFieldCount(1);//将函数模板放入到定义范围中 // Prototype将函数映射到js中,Nan::SetPrototypeMethod第一个参数是函数模板,第二个参数为js调用函数名称,第三个参数为c语言中的函数名称 Nan::SetPrototypeMethod(tpl, "value", GetValue); Nan::SetPrototypeMethod(tpl, "plusOne", PlusOne); Nan::SetPrototypeMethod(tpl, "multiply", Multiply); constructor.Reset(tpl->GetFunction()); exports->Set(Nan::New("MyObject").ToLocalChecked(), tpl->GetFunction());//类似前文提到的对象工厂构造方法,将objects对象推送出去 } void MyObject::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {//定义MyObject对象 if (info.IsConstructCall()) { // Invoked as constructor: `new MyObject(...)` double value = info[0]->IsUndefined() ? 0 : info[0]->NumberValue(); MyObject* obj = new MyObject(value); obj->Wrap(info.This()); info.GetReturnValue().Set(info.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; v8::Local<v8::Value> argv[argc] = { info[0] }; v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor); info.GetReturnValue().Set(cons->NewInstance(argc, argv)); } } void MyObject::GetValue(const Nan::FunctionCallbackInfo<v8::Value>& info) { MyObject* obj = ObjectWrap::Unwrap<MyObject>(info.Holder()); info.GetReturnValue().Set(Nan::New(obj->value_)); } void MyObject::PlusOne(const Nan::FunctionCallbackInfo<v8::Value>& info) { MyObject* obj = ObjectWrap::Unwrap<MyObject>(info.Holder()); obj->value_ += 1; info.GetReturnValue().Set(Nan::New(obj->value_)); } void MyObject::Multiply(const Nan::FunctionCallbackInfo<v8::Value>& info) { MyObject* obj = ObjectWrap::Unwrap<MyObject>(info.Holder()); double multiple = info[0]->IsUndefined() ? 1 : info[0]->NumberValue(); v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor); const int argc = 1; v8::Local<v8::Value> argv[argc] = { Nan::New(obj->value_ * multiple) }; info.GetReturnValue().Set(cons->NewInstance(argc, argv)); }
在addon.cc中只需要定义初始化函数
#include <nan.h> #include "myobject.h" void InitAll(v8::Local<v8::Object> exports) { MyObject::Init(exports); } NODE_MODULE(addon, InitAll)
最后完成对对象的调用addon.js
var addon = require('bindings')('addon'); var obj = new addon.MyObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 console.log( obj.multiply().value() ); // 13 console.log( obj.multiply(10).value() ); // 130 var newobj = obj.multiply(-1); console.log( newobj.value() ); // -13 console.log( obj === newobj ); // false