Skip to content

Latest commit

 

History

History
276 lines (219 loc) · 12.1 KB

node-native.org

File metadata and controls

276 lines (219 loc) · 12.1 KB

node native 模块编写笔记

1 简介

在这里搜索到的只有这篇 文章,所以本篇文档的初衷是总结一下自己学习node native模块写法。希望能帮助到大家一些,省去一些搜索时间。

二进制模块也是通过require机制进行加载,关于模块加载机制,这是 一篇不错的文章。

对下面repo的作者表示感谢:

2 Hello World

事实上这个Hello World连个hello world都没打而是建个“空壳子”模块,可以通过require引入,但这个对象什么都没有。

2.1 代码说明

#include <v8.h>
#include <node.h>

using namespace v8; 

extern "C" {
  static void Init(Handle<Object> target) {
  }   
  NODE_MODULE(helloworld, Init)
}
import Options
from os import unlink, symlink, popen
from os.path import exists 
from shutil import copy2


built = 'build/Release/helloworld.node'
dest = 'lib/helloworld.node'
def set_options(opt):
    opt.tool_options("compiler_cxx")

def configure(conf):
    conf.check_tool("compiler_cxx")
    conf.check_tool("node_addon")

def build(bld):
    obj = bld.new_task_gen("cxx", "shlib", "node_addon")
    obj.target = "helloworld"
    obj.source = "src/firststep.cc"

def shutdown():
  if Options.commands['clean']:
    if exists('helloworld.node'):
      unlink('helloworld.node')
  else:
    if exists(built):
      copy2(built, dest)

之所以将入口函数放在extern “C”中是告诉g++不要对这个函数名“动手脚”,不要最后变成一个“长相奇怪“的函数名。这个“长相奇怪”大家用nm命令看一下就知道了,不过我试过拿掉extern “C”运行也OK,不过谨慎点还是加上吧。 Init 函数名是可以任意起的,只不过是被 NODE_MODULE 宏指定为入口地方,参数target对象即是这个模块要返回的对象。也即,编写node native模块就是对target对象进行“包装打扮”。V8给咱送个对象过来,说交给你们了,我们再给这个对象“打扮打扮”再export到JS层。大体就是这么个回事。这个 Handle 大家姑且就权当一个智能指针吧。自动管理内存了。

NODE_MODULE 这个宏在Node.js 版本(下同)0.6.19-release中定义如下:

250 #define NODE_MODULE(modname, regfunc)                                 \
251   extern "C" {                                                        \
252     NODE_MODULE_EXPORT node::node_module_struct modname ## _module =  \
253     {                                                                 \
254       NODE_STANDARD_MODULE_STUFF,                                     \
255       regfunc,                                                        \
256       NODE_STRINGIFY(modname)                                         \
257     };                                                                \
258   }
src/node.h

有兴趣可以继续跟踪展开。

2.2 编译测试

代码的结构用tree命令输出如下:

.
├── lib
├── src
│   └── firststep.cc 
└── wscript

将原码都放于 src 下。编译测试步骤如下:

   node-waf configure build
   node #进入node repl,在repl中输入
   require(‘./lib/helloworld'); #如果编译正常,正确的输出应该如下:
   {} #一个空对象
   <c-d>返回终端
   node-waf distclean #删除生成的中间文件。再运行tree命令,此时lib中已包含我们需要的二进制模块
.
├── lib
│   └── helloworld.node
├── src
│   └── firststep.cc
└── wscript

2 directories, 3 files

如果再包装一下,加个package.json,main字段指向lib/helloworld.node就可以直接require文件夹来使用这个native扩展。

至此,一个模块的怎么写框架算是有了,下一步就是怎样包装这个返回的target。

3 primitive数据类型的添加

首先看下V8数据类型家族谱如下图所示: ./include/images/v8data.png

所以primitive数据类型有三种,String/Number/Boolean,下面的代码说明怎样将简单数据类型绑定到target对象上。

#include <math.h>
target->Set(String::NewSymbol("pi"),
                  Number::New(M_PI)); //in math.h

target->Set(String::NewSymbol("PI"),
                  Number::New(M_PI),
                  static_cast<v8::PropertyAttribute>(v8::ReadOnly|v8::DontDelete));
 NODE_DEFINE_CONSTANT(target, CONSTANTS);
 target->Set(String::New("Hofsadter"), String::New("I am a strange loop"));

NODE_DEFIN_CONSTANT 宏定义在Node.js/src/node.h中,

118 #define NODE_DEFINE_CONSTANT(target, constant)                            \
119   (target)->Set(v8::String::NewSymbol(#constant),                         \
120                 v8::Integer::New(constant),                               \
121                 static_cast<v8::PropertyAttribute>(                       \
122                     v8::ReadOnly|v8::DontDelete))   

第二个参数只能是宏,而且值还只能是Interger,这个就有点蛋疼了,所以,绑定primitive的方法基本还是原始的调用V8语句。Object Set 的API见这里。 第三个参数指定这个值的属性,如’r–’的权限可以像上面事例的 PI 定义一样设定第三个参数。具体测试这里就省去了,方法跟测试上面的空壳子是一样的。

4 Array的绑定

Local<Array> resultArray = Array::New(3);
for (size_t i = 0; i < 3; ++i) {
    Local<Value> suffix = Number::New(i);
    Local<Value> str = String::Concat(String::New("array element"), suffix->ToString());

    resultArray->Set(i, str);
}   
target->Set(String::New("array_test"), resultArray);

5 同步function的绑定

简单讲就是能提供一个接口供JavaScript使用,当JavaScript需要完成某件事情时直接调用便能有一个同步返回。这种方法的使用多见于计算量大或者与OS交互居多。

实现步骤主要有两步:

  • 完成这个function实现的具体逻辑
  • 将这个function绑定到target上成为返回对象的一个属性。而这个属性是个方法。

下文分别用代码说明这两个步骤的注意点。

5.1 function的建立

 Handle<Value> fetchConfig(const Arguments &args) {
	HandleScope scope;
       //your process logic here
	return scope.Close(String::New("Here is your string, your majesty"));
 }

需要注意以下几点:

  • 函数的返回: 函数的返回是Value指针,同样由Handle来处理GC。
  • 函数的参数: 参数都长一个样,但是可以根据你在JavaScript中传的值可以进行不同数据类型之间的转换。Arguments API最常用的有: []重载操作符,Length()和Holder()。Length()用于Validate,然后用[]取对应JavaScript中传入的参数并转换成相应类型,然后你的处理逻辑…
  • 函数体: 最开始在栈上创建一个诸多Handle的管理者,名叫HandleScope ,这样也方便管理,当收掉这个scope时候scope里的所有Handle都会被GCed。scope.Close(),可带参数,这个参数用于返回给从这个scope回去时的那个scope,一般是global/persistent scope。

5.2 function的绑定

Handle<FunctionTemplate> fetTmpl = FunctionTemplate::New(fetchString);
target->Set(String::New("fetchString"), fetTmpl->GetFunction());

//or use a Node.js marco
NODE_SET_METHOD(target, "propertyName", functionName)
//which is defined at src/node.h as below:
124 #define NODE_SET_METHOD(obj, name, callback)                              \
125   obj->Set(v8::String::NewSymbol(name),                                   \
126            v8::FunctionTemplate::New(callback)->GetFunction())

从代码中可以看出,绑定function可以很方便的用Node.js宏来实现。FunctionTemplate API 中对GetFunction() API的说明是在当前执行上下文中创建一个全局唯一的function 实例

同步function的绑定大概如此。

6 Object的绑定

这里的Object是指用new创建Object的方法,因为之前function也是Object。

与前一节的function绑定类似,同样用到 FunctionTemplate ,只不过更多的是 FunctionTemplate APIInstanceTemplate 获得一个object template,然后对object template添加“private”字段和get/set method。

可以通过下面例子加以说明:

Handle<Value> IronMan(const Arguments &args) {
	if (!args.IsConstructCall()) {
		return ThrowException(Exception::TypeError(String::New("you should use new operator to create a new obj")));
		
	}
    return args.This();
}

Handle<Value> getAge(const Arguments &args) {
	HandleScope scope;
	Handle<Object> This = args.This();
	int age = This->Get(String::New("age"))->Uint32Value();
	return scope.Close(Integer::New(age));
}

Handle<Value> setAge(const Arguments &args) {
	HandleScope scope;
	Handle<Object> This = args.This();
	This->Set(String::New("age"), Integer::New(args[0]->Uint32Value()));
	return Undefined();
}
extern "C" {
    static void Init(Handle<Object> target) {
        HandleScope scope;
		Handle<FunctionTemplate> ironMan = FunctionTemplate::New(IronMan);
        Handle<ObjectTemplate> toniStark = ironMan->InstanceTemplate();
		toniStark->SetInternalFieldCount(1);
        toniStark->Set(String::New("age"), Integer::New(0));
		NODE_SET_PROTOTYPE_METHOD(ironMan, "getAge", getAge);
		NODE_SET_PROTOTYPE_METHOD(ironMan, "setAge", setAge);
        target->Set(String::NewSymbol("IronMan"),
                    ironMan->GetFunction());
    }
    NODE_MODULE(simpleobject, Init)
}

对对象的模仿我也就看到这里,跟自己的应用没有多大兴趣。其实还有ObjectWrap可以继承。暂时没有用到,先点到这里,有需要的继续。

7 异步

涉及到libuv 具体可以参见这个repo的介绍,这里就不详述。

8 从C++内部发signal

这个repo叙述的很详细,就不画蛇添足了。