用C/C++来实现 Node.js 的模块(一)
N久之前的一个坑——用Node.js来重构NBUT的OnlineJudge,包括评测端也得重构一遍。(至于什么时候完成大家就不要关心了,(/‵Д′)/~╧╧
总之我们现在要做的其实简而言之就是——用C/C++来实现Node.js的模块。
准备工作
工欲善其事,必先~~耍流氓~~利其器。
node-gyp
首先你需要一个node-gyp模块。
在任意角落,执行:
$npminstallnode-gyp-g
在进行一系列的blahblah之后,你就安装好了。
Python
然后你需要有个python环境。
自己去官网搞一个来。
注意:根据node-gyp的GitHub显示,请务必保证你的python版本介于2.5.0和3.0.0之间。
编译环境
嘛嘛,我就偷懒点不细写了,还请自己移步到node-gyp去看编译器的需求。并且倒腾好。
入门
我就拿官网的入门HelloWorld说事儿了。
HelloWorld
请准备一个C++文件,比如就叫~~sb.cc~~hello.cc。
然后我们一步步来,先往里面搞出头文件和定义好命名空间:
#include<node.h> #include<v8.h> usingnamespacev8;
主要函数
接下去我们写一个函数,其返回值是Handle<Value>。
Handle<Value>Hello(constArguments&args) { //...嗷嗷待写 }
然后我来粗粗解析一下这些东西:
Handle<Value>
做人要有节操,我事先申明我是从这里(@fool)参考的。
V8里使用Handle类型来托管JavaScript对象,与C++的std::sharedpointer类似,Handle类型间的赋值均是直接传递对象引用,但不同的是,V8使用自己的GC来管理对象生命周期,而不是智能指针常用的引用计数。
JavaScript类型在C++中均有对应的自定义类型,如String、Integer、Object、Date、Array等,严格遵守在JavaScript中的继承关系。C++中使用这些类型时,必须使用Handle托管,以使用GC来管理它们的生命周期,而不使用原生栈和堆。
而这个所谓的Value,从V8引擎的头文件v8.h中的各种继承关系中可以看出来,其实就是JavaScript中各种对象的基类。
在了解了这件事之后,我们大致能明白上面那段函数的申明的意思就是说,我们写一个Hello函数,其返回的是一个不定类型的值。
注意:我们只能返回特定的类型,即在Handle托管下的String啊Integer啊等等等等。
Arguments
这个就是传入这个函数的参数了。我们都知道在Node.js中,参数个数是乱来的。而这些参数传进去到C++中的时候,就转变成了这个Arguments类型的对象了。
具体的用法我们在后面再说,在这里只需要明白这个是个什么东西就好。(为毛要卖关子?因为Node.js官方文档中的例子就是分开来讲的,我现在只是讲第一个HelloWorld的例子而已(´థ౪థ)σ
添砖加瓦
接下去我们就开始添砖加瓦了。就最简单的两句话:
Handle<Value>Hello(constArguments&args) { HandleScopescope; returnscope.Close(String::New("world")); }
这两句话是什么意思呢?大致的意思就是返回一个Node.js中的字符串"world"。
HandleScope
同参考自这里。
Handle的生命周期和C++智能指针不同,并不是在C++语义的scope内生存(即{}包围的部分),而需要通过HandleScope手动指定。HandleScope只能分配在栈上,HandleScope对象声明后,其后建立的Handle都由HandleScope来管理生命周期,HandleScope对象析构后,其管理的Handle将由GC判断是否回收。
所以呢,我们得在需要管理他的生命周期的时候申明这个Scope。好的,那么为什么我们的代码不这么写呢?
Handle<Value>Hello(constArguments&args) { HandleScopescope; returnString::New("world"); }
因为当函数返回时,scope会被析构,其管理的Handle也都将被回收,所以这个String就会变得没有意义。
所以呢V8就想出了个神奇的点子——HandleScope::Close(Handle<T>Value)函数!这个函数的用处就是关闭这个Scope并且把里面的参数转交给上一个Scope管理,也就是进入这个函数前的Scope。
于是就有了我们之前的代码scope.Close(String::New("world"));。
String::New
这个String类所对应的就是Node.js中原生的字符串类。继承自Value类。与此类似,还有:
•Array
•Integer
•Boolean
•Object
•Date
•Number
•Function
•...
这些东西有些是继承自Value,有些是二次继承。我们这里就不多做研究,自己可以看看V8的代码(至少是头文件)研究研究或者看看这个手册。
而这个New呢?这里可以看的。就是新建一个String对象。
至此,这个主要函数我们就解析完毕了。
导出对象
我们来温习一下,如果是在Node.js里面写的话,我们怎么导出函数或者对象什么的呢?
exports.hello=function(){}
那么,在C++中我们该如何做到这一步呢?
初始化函数
首先,我们写个初始化函数:
voidinit(Handle<Object>exports) { //...嗷嗷待写你妹啊!#゚Å゚)⊂彡☆))゚Д゚)・∵ }
这是龟腚!函数名什么的无所谓,但是传入的参数一定是一个Handle<Object>,代表我们下面将要在这货上导出东西。
然后,我们就在这里面写上导出的东西了:
voidinit(Handle<Object>exports) { exports->Set(String::NewSymbol("hello"), FunctionTemplate::New(Hello)->GetFunction()); }
大致的意思就是说,为这个exports对象添加一个字段叫hello,所对应的东西是一个函数,而这个函数就是我们亲爱的Hello函数了。
用伪代码写直白点就是:
voidinit(Handle<Object>exports) { exports.Set("hello",functionhello); }
大功告成!
(大功告成你妹啊!闭嘴(‘д‘⊂彡☆))Д´)
真·导出
这才是最后一步,我们最后要申明,这个就是导出的入口,所以我们在代码的末尾加上这一行:
NODE_MODULE(hello,init)
纳了个尼?!这又是什么东西?
别着急,这个NODE_MODULE是一个宏,它的意思呢就是说我们采用init这个初始化函数来把要导出的东西导出到hello中。那么这个hello哪来呢?
它来自文件名!对,没错,它来自文件名。你并不需要事先申明它,你也不必担心不能用,总之你的这个最终编译好的二进制文件名叫什么,这里的hello你就填什么,当然要除去后缀名了。
详见官方文档。
NotethatallNodeaddonsmustexportaninitializationfunction:
voidInitialize(Handle<Object>exports); NODE_MODULE(module_name,Initialize)
Thereisnosemi-colonafterNODE_MODULEasit'snotafunction(seenode.h).
Themodule_nameneedstomatchthefilenameofthefinalbinary(minusthe.nodesuffix).
编译(๑•́₃•̀๑)
来吧,让我们一起编译吧!
我们再新建一个类似于Makefile的归档文件吧——binding.gyp。
并且在里面添加这样的代码:
{ "targets":[ { "target_name":"hello", "sources":["hello.cc"] } ] }
为什么这么写呢?可以参考node-gyp的官方文档。
configure
在文件搞好之后,我们要在这个目录下面执行这个命令了:
$node-gypconfigure
如果一切正常的话,应该会生成一个build的目录,然后里面有相关文件,也许是M$VisualStudio的vcxproj文件等,也许是Makefile,视平台而定。
build
Makefile也生成好之后,我们就开始构造编译了:
$node-gypbuild
等到一切编译完成,才算是真正的大功告成了!不信你去看看build/Release目录,下面是不是有一个hello.node文件了?没错,这个就是C++等下要给Node.js捡的肥皂!
搞基吧!Nodeヽ(✿゚▽゚)ノC++
我们在刚才那个目录下新建一个文件jianfeizao.js:
varaddon=require("./build/Release/hello"); console.log(addon.hello());
看到没!看到没!出来了出来了!Node.js和C++搞基的结果!这个addon.hello()就是我们之前在C++代码中写的Handle<Value>Hello(constArguments&args)了,我们现在就已经把它返回的值给输出了。
洗洗睡吧,下节更深入
时间不早了,今天就写到这里了,至此为止大家都能搞出最基础的Helloworld的C++扩展了吧。下一次写的应该会更深入一点,至于下一次是什么时候,我也不知道啦其实。
(喂喂喂,撸主怎么可以这么不负责!(o゚ロ゚)┌┛Σ(ノ´ω`)ノ