使用C++模块为Nodejs添彩助力

cover-image

前言

了解node的开发者清楚, node不是一门语言, 而是一个平台. 多亏了libuv屏蔽了操作系统层面的差异, 优秀的js语言特性(有些人不以为然, 不评论)能让node做很多事情. 我们知道node擅长的部分是异步IO, 这里还是要感谢libuv, 但是不擅长的是CPU密集型操作, 这作为解释型脚本语言的弱项, 静态语言则反之表现出色.

Node的C/C++模块

在朴老师的深浅node中指出, node with c++ module的fab数列计算数列与c语言不相上下, 当然C/C++作为node这一平台的补充是完全没有问题的, 但是这里朴老师明显是有误导读者的嫌疑(“感觉”node的计算速度比得上c). C/C++模块的另一大用处是可以获得系统的完全操作权限, 使用C的第三方lib, 完成js不能完成的任务, 例如线程层面的Sleep, 以及扩展node支持不好的crypto模块等等. 但是使用C/C++模块的时候, 要认识到我们其实是绕开了js/libuv提供的一些便利, 所以在开发的时候, 需要特别注意平台兼容性, 内存管理等等.

准备

之前给腾讯的微校开发CET无准考证查询功能, 由于api使用了加密手段, 导致调用起来比较麻烦, 得知其加密手法为CFB模式下64位的DES, 稍微查了一下node手册的crypto模块, 没有发现node对这个方法的支持, 而且考虑到js对加解密计算的弱项, 便打算借助C/C++模块配合openssl实现. 我们使用node-gyp工具进行模块的交叉编译, 由于nodejs4.0以上使用了新的C++ Compiler11标准, 所以需要更新到gcc >=4.8才能对第三方C/C++模块进行编译, centos安装devtoolset3, ubuntu使用ppa源升级, 我在上一篇文章提到了如何升级.

NAN模块

由于node各个版本使用的V8版本差异, 所以编写C/C++模块的时候语法差异比较大, 这里我们使用nan这个包抹平这些差异, nan提供了一个包含很多抹平差异宏定义实现的nan.h文件, 所以我们只要了解NAN这一套就可以了.

1
npm i nan --save

然后新建一个binding.gyp文件.

1
2
3
4
5
6
7
8
9
10
11
{
"targets": [
{
"target_name": "cetdecoder-main",
"sources": [ "cetdecoder-main.cc" ],
"include_dirs": [
"<!(node -e \"require('nan')\")"
]
}

]
}

C/C++

先创建一个cc文件, 来看看里面的内容

cet.cc

这里添加了几个函数, 它的参数类型是

Nan::FunctionCallbackInfo& info```, 然后Init方法里的`exports`方法把这两个函数以"decodeTicket"和"encodeRequest"作为方法名暴露给node.
1

这里可以看到, 从`char*`建立一个v8中String类型对象的方法是: `Nan::New("string").ToLocalChecked()`, 相同的初始化函数是`Nan::New<v8::FunctionTemplate>(func)->GetFunction()`.  

最后的`NODE_MODULE`宏完成整个对象的暴露, 这一句与`Init`方法相当于node中的

``` javascript
module.exports = {
  foo: bar
}

再来看具体方法内的操作方法, 这里我们要关心的是如何将js中也就是V8的数据类型转化成c/c++的数据类型.

func

这个函数的需求是传入一个Buffer, 对Buffer中的二进制数据进行位解码操作后返回对应的Buffer.

这里比较惭愧, 我翻了手册好几遍都没找到如何转化Buffer对象的方法(粗粗看了一下, doc的位置在node_modules/nan/doc), 为了方便, 我这里就传入一个数组, 例如[0x41, 0x6d, 0x44, ...], 然后返回另一个数组.

当然我也没有找到如何转化数组的方法, 但是我找到了一个args.Length()的方法, 于是借助js的apply, 再在C中使用一个循环就ok了. 这里具体的获取某一个参数方法为info[i]->NumberValue(), 查V8手册可知返回值是一个double类型, 这里我们根据需要转化成char类型的一个数组.

接下来是返回的数组的创建.

1
2
3
4
5
v8::Local<v8::Array> retArr = Nan::New<v8::Array>(len);
for (i = 0; i < len; i++) {
Nan::Set(retArr, i, Nan::New(ret[i]));
}
info.GetReturnValue().Set(retArr);

编译

1
node-gyp configure
node-gyp build

也可以使用一句话build

1
node-gyp configure build

生成的.node文件位于./build/Release, 可在node中直接require

可以在package.json中加入"gypfile": true让其自动编译

需要注意的地方

其实这里我并没有使用NAN的example, nan的标准创建函数方法是使用NAN_METHOD宏.我建议参照NAN官方写法为了升级方便.

后记

因为做的比较急, 对V8的了解还不够深刻, 我甚至没有C++基础(只有很少的C), 例如Scope等概念还不是很清楚. 所以C/C++模块的开发对大部分前端工程师来说, 还真是一个技术活.

这里是我的一个粗浅记录, 如果有什么不恰当的欢迎指出.

发表于我的博客Zero’s Corner.