一直想要开一个博客,总结记录一下自己学到的东西,今天终于动笔写了第一篇,希望能够坚持下去。
我的博客主要会分享一些自己最近学习的东西,主要是给自己看的,如果能帮到别人的话当然最好了。
----------------------我是华丽的分割线-------------------------------
实验室最近正在做一个基于Node.js的项目,之前对Front End的知识了解很少,所以从JavaScript一点点学起慢慢熟悉。 我的主要任务是把一个已经写好的C语言程序转化为Node.js的Library可以随时调用运行。
按照Node官方给的文档,我从最基础的Hello World开始,学习如何添加Addon,以下是结合官方的documentation我自己的总结。
Node.js Addons 是C/C++写成的Objects,可以通过require()调用,用起来就像Node自带的module一样,主要用于提供介于Node.js和C/C++库之间的接口。
实现Addons所需要的背景知识主要包括:V8 JavaScript Engine, Libuv, Node.js内部library, Node.js内部库自带一些C/C++ API可以供Addons使用。
从最简单的开始,添加一个C++ Addon实现的功能等同于: module.exports.hello = function(){return ‘world‘;}; 调用这个函数,可以返回字符串“world”;
Step1
首先需要通过运行命令’npm init‘创建一个 package.json文件的框架,用来记录所需的配置文件
Step2
安装 “NAN”。 NAN是一个介于 C++源程序,Node,和 V8 API之间的抽象层。NAN的存在是为了保证当V8的API更新后,之前的程序仍可以运行。 当V8的API更新后,只需更新NAN便可以保证已经写好的程序可以继续运行,避免了修改源代码的问题。
通过运行‘npm install [email protected] --save‘来安装NAN,并将NAN作为 dependency存储在package.json里。
Step3
所有的Addons 都会使用[node-gyp]编译,在package.json中加入‘gypfile:true’来使能nodel-gyp编译。 当 node-gyp被调用时,会寻找一个与‘package.json’在同一目录下的‘binding.gyp’ file。 这个binding文件用YAML写成,用于描述build的细节,比如源文件以及所需的dependencies。node-gyp会参考我们自己写成的binding.gyp文件和一个common.gypi来进行编译,common.gypi描述了node.js会用到的设置和可能的dependencies。 因为我们的代码将会在node下编译,所以必须要有common.gypi file用于描述node的环境设置。另外,我们还需要下载和我们正在运行的版本对应的tarball。(原文是it must also download the complete tarball of the particular release you are running,我也没有很理解这句话,看懂的朋友希望指正)。
文档中给出了简单的binding文件:
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
span.s1 { }
{
"targets": [
{
"target_name": "hello",
"sources": [ "hello.cc" ],
"include_dirs": [
"<!(node -e \"require(‘nan‘)\")"
]
}
]
}
我们只要新建一个binding.gyp file并复制这段代码就可以了。
这段代码做了如下几件事:1 命名目标Addon为“hello”,最终输出的文件名将会是hello.node. 2 需要编译的源文件是"hello.cc" 3 规定了编译时[NAN]的路径
Step4
编写hello.cc的源代码。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b; min-height: 13.0px }
span.s1 { }
#include <nan.h>
using namespace v8;
NAN_METHOD(Method) {
NanScope();
NanReturnValue(String::New("world"));
}
void Init(Handle<Object> exports) {
exports->Set(NanSymbol("hello"), FunctionTemplate::New(Method)->GetFunction());
}
NODE_MODULE(hello, Init)
现在从下到上来分析这段代码:
NODE_MODULE(hello,Init)定义了addon的entry-point, 参数“hello”必须与binding file中的target相同,第二个参数“Init“指向了要调用的函数。
void Init(Handle<Object> exports) {
exports->Set(NanSymbol("hello"), FunctionTemplate::New(Method)->GetFunction());
}
按照NODE_MODULE的定义,这一函数是addon实际的entry-point。 这一函数有两个参数: ‘exports’ 与js文件中的‘module.exports‘相同,第二个参数(本例中已经被忽略)是‘module’,类似js文件中的‘module’。一般情况下,我们会给exports attach一些属性,但是我们也可以使用‘module’(第二个参数)来替换‘module’的exports属性,这样就只能 export a single thing, e.g. module.exports = function(){}.
本例中, 我们只需要attach一个“hello” property 在module.exports中。所以我们给V8 的一个‘Function’对象[SET]一个 V8’String‘ 属性。 首先,我们使用 NanSymbol() 函数新建一个’symbol‘ 字符串(之后可以重复使用). 我们使用 V8 ‘FunctionTemplate‘ 把一个普通的 C++函数转换为一个 V8-callable function. 在我们的例子中,这个‘C++函数’是Method function。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
span.s1 { }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
span.s1 { }
NAN_METHOD(Method) {
NanScope();
NanReturnValue(String::New("world"));
}
这里体现出了NAN的用处。 V8 API的改变使得 将C++ Addon 添加到不同版本的node变得很困难。 所以NAN提供了一个简单的mapping,我们可以定义一个 ‘FunctionTemplate’可以接受的 V8 compatible function。 在最近的V8中,NAN_METHOD(Method) 可以扩展成 ‘void Method(const v8::FunctionCallbackInfo<v8::Value>& args)‘,这是一个 V8 callable function的标准signature。‘args‘ 包括了 call information, 比如 JavaScript function的参数,设置返回值等。
NanScope() 定义了新建的‘handles’的生命周期。当我们在函数开始时使用NanScope时表示所有的 我们使用的V8 object存活的时间与该function相等。 如果没有这句代码,V8 Object将不会在function结束时被回收。
NanReturnValue设置了函数的返回值。在本例子中, 我们新建了一个简单的“world” 字符串,这个字符串将会被暴露为标准的JavaScript String。
Step 5
编译我们的Addon。 通过‘sudo npm install node-gyp -g‘ 安装‘node-gyp‘。
运行‘node-gyp configure‘ 来设置build fixtures. 会生成一个 ‘Makefile‘ 文件
运行‘node-gyp build‘启动编译过程。 或者可以使用‘node-gyp rebuild‘ 将 ‘configure‘ & ’build‘和成一步。成功以后我们就有了一个可以使用的addon binary文件。Node可以载入并运行这个文件像载入普通.js module file 一样。
Step 6
编写JavaScript
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b; min-height: 13.0px }
span.s1 { }
var addon = require(‘./build/Release/hello.node‘);
console.log(addon.hello());
调用addon的hello()function,输出“world”
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
span.s1 { }