对于前端开发工程师来说,c/c++有些生疏或晦涩,但如果能够掌握,当模块出现性能瓶颈时,将对你有极大帮助。
Javascript的一个典型弱点是位运算。它的实现是参考Java的位运算实现,Java的位运算是通过Int类型进行比较,而Javascript只有double类型的数据,它需要先把double类型转换成int类型才能进行位运算比较。所以在Javascript层面进行位运算效率不高。
在应用中,会频繁出现位运算操作,例如编码,转码等逻辑,如果通过javascript实现,会耗费大量cpu。如果通过编写c/c++扩展模块,那么就能提升性能。
下面介绍c/c++扩展模块的编写,编译,加载和导出过程。
介绍之前,先明确,编写的c/c++代码最好是跨平台,通过*nix和windows上不同的编译工具,生成.node文件, 再通过process.dlopen加载该文件,从而实现不同平台加载c/c++模块。
2.4.1 前提条件
编写c++需要一定的功底,下面这些如果能够了解,可以让你在编写过程中事半功倍。
GYP项目生成工具。
是一个跨平台的项目生成器。即 “Generate Your Projects”的缩写。它可以帮你生成不同平台的项目文件。比如windows下的.sln,Mac下的Xcode等。
V8引擎c++库。
V8是Node自身动力来源。它自身是由c++实现,实现Javascript与c++的相互调用。
libuv库。
Node能够实现跨平台的原因就是使用了libuv库。这个库是跨平台,它底层封装了不同平台的实现。
Node内部库。
Node自身提供的c++代码,可以把它当作工具类来使用。
其他库。
在deps目录下存在zlib,openssl,http_parser等。
2.4.2 c/c++扩展模块的编写
Javascript原型代码如下:
exports.sayHello = function(){
return "Hello world";
}
新建hello目录作为自己的项目目录,编写hello.cc并将其存储到src目录下,代码如下:
#include <node.h>
#include <v8.h>
using namespace v8;
Handle<Value> SayHello(const Argument& args){
HandleScope scope;
return scope.Close(String::New("Hello world!"));
}
// 给传入的目标对象添加sayHello()方法
void Init_Hello(Handle<Object> target){
target->Set(String::NewSymbol("sayHello"),FunctionTemplate::New(SayHello)->GetFunction());
}
// 调用NODE_MODULE()方法将注册方法定义到内存中
NODE_MODULE(hello,Init_Hello)
通过dlopen()来动态加载,然后导出给Javascript调用。
2.4.3 c/c++扩展模块的编译
在GYP工具帮助下,c/c++扩展模块的编译是一件省心的事,无须为每个平台编写不同的项目文件。具体内容如下:
{
'target':[
{
'target_name':'hello',
'sources':[
'src/hello.cc'
],
'conditions':[
['OS == "win"',
{
'libraries':['-lnode.lib']
}
]
]
}
]
}
然后调用:
$ node-gyp configure
该命令会在当前目录创建build目录,并生成系统相关的项目文件。
在*nix平台下,build目录中会出现makefile文件;在windows下,则会生成vcxproj等文件。
继续执行
$ node-gyp build
编译过程会根据不同平台,分别通过make或vcbuild进行编译。编译后hello.node文件会生成在build/Release目录下。
2.4.4 c/c++扩展模块的加载
得到hello.node后,通过执行如下代码就可以:
var hello = require('./build/Release/hello.node');
console.log(hello.sayHello());
c/c++扩展模块由于已经编译为.node文件(实际是.so或.dll文件),加载速度比javascript文件会快,这里其实是省略了文件的编译过程。
另外使用c/c++模块好处在于可以灵活动态地加载它们,保存Node模块自身简单性的同时,给予Node无限的扩展性。