Node.js不仅可以加载JavaScript库,还可以使用原生模块(已编译的C / C ++代码)进行扩展。虽然这并不意味着您应该清理现有的JavaScript模块,转而使用良好的C ++语言,但是这些知识可能会在特定用例中派上用场。
首先:可以直接访问现有的C / C ++库。而不是像“执行命令”风格那样将这些应用程序称为外部应用程序,直接使用现有的源代码,并以JavaScript运行时可理解的形式将结果传递回Node.js。这样您也可以访问操作系统的低级API
第二:性能。在许多情况下,编写良好的本机代码可能比JavaScript代码更快,性能更高。
接下来,我们通过一个短小的例子,来准备将一小段c++的代码编译为node的native module。
这里,我们首先会用到Nan(Native Abstractions for Node.js)。它基本上是一个C++头文件,提供了一个命名空间和一组有用的宏,以便与V8 API交互(Node.js中使用的JavaScript引擎)。它也使得你的代码可以面向未来(针对Node.js的下一个版本),因为V8的API在版本之间往往会发生巨大的变化。Nan是Node.js API文档中推荐的解决方案。
首先,我们创建一个.cpp文件(我把它命名为main.cpp),并填入下面的一段代码:
#include <nan.h>
// NAN_METHOD is a Nan macro enabling convenient way of creating native node functions.
// It takes a method's name as a param. By C++ convention, I used the Capital cased name.
NAN_METHOD(Hello) {
// Create an instance of V8's String type
auto message = Nan::New("Hello from C++!").ToLocalChecked();
// 'info' is a macro's "implicit" parameter - it's a bridge object between C++ and JavaScript runtimes
// You would use info to both extract the parameters passed to a function as well as set the return value.
info.GetReturnValue().Set(message);
}
// Module initialization logic
NAN_MODULE_INIT(Initialize) {
// Export the `Hello` function (equivalent to `export function Hello (...)` in JS)
NAN_EXPORT(target, Hello);
}
// Create the module called "addon" and initialize it with `Initialize` function (created with NAN_MODULE_INIT macro)
NODE_MODULE(addon, Initialize);
请注意,在C++中创建的方法的返回值是void,它们不显式返回任何值。而是在“桥接器” info对象上设置返回值(引用的类型是Nan::FunctionCallbackInfo,“隐式地”在NAN_METHOD宏中传递。
为了运行代码,你仍然需要编译它。不用担心,您不必手动调用C++编译器。Node.js会帮你完成。
为了方便后续的操作,我们通过npm在main.cpp所在目录下创建一个node的项目:
npm init -y
接下来,安装Nan和node-gyp(你已经知道了Nan; node-gyp是一组本地代码编译工具):
npm install nan node-gyp --save
更新你的package.json文件,使其包含以下几行:
{
"name": "node-native-addons-example",
"version": "1.0.0",
"dependencies": {
"nan": "^2.6.1",
"node-gyp": "^3.6.0"
},
"scripts": {
"compile": "node-gyp rebuild",
"start": "node main.js"
},
"gypfile": true
}
注意,以下几行是手动添加:
"scripts": {
"compile": "node-gyp rebuild",
"start": "node main.js"
},
"gypfile": true
"compile": "node-gyp rebuild"
- 用于C ++代码编译
"start": "node main.js"
- 为我们的主要可执行脚本
现在创建一个bindings.gyp文件 - 这是node-gyp的一个配置文件。注意它是如何在”sources”下指定源文件”main.cpp”的:
{
"targets": [
{
"include_dirs": [
"<!(node -e \"require('nan')\")"
],
"target_name": "addon",
"sources": [ "main.cpp" ]
}
]
}
你还需要在你的机器上安装这三个工具(你在MacOS上应该没问题 - 它们是预安装的;它们也很容易安装在Linux上,例如Ubuntu和apt-get):
如果缺少这些信息,node-gyp会报告它,所以不要浪费你的时间来确定你的操作系统上是否有它们。
现在是第一次编译的时候了
npm run compile
(记住,它会启动node-gyp并编译你的C ++源bindings.gyp文件)。如果一切顺利,你应该看到类似的输出:
npm run compile
> temp@1.0.0 compile /Users/caishichao/Applications/temp
> node-gyp rebuild
CXX(target) Release/obj.target/addon/main.o
SOLINK_MODULE(target) Release/addon.node
clang: warning: libstdc++ is deprecated; move to libc++ with a minimum deployment target of OS X 10.9 [-Wdeprecated]
很好,你已经编译了你的第一个native module。我们来创建一个JavaScript文件(test.js),您将使用Node.js运行:
// note that the compiled addon is placed under following path
const {Hello} = require('./build/Release/addon');
// `Hello` function returns a string, so we have to console.log it!
console.log(Hello());
并运行:
node test.js
Hello from C++!
当然。你想知道本地模块真正发光的地方。
为此我们实现一个简单的函数,该函数将检查给定的数字是否是质数,并返回布尔值(true或false)。我们将对函数的C ++和JavaScript版本使用相同的算法,并最终比较相同的相对较大的执行时间。
该算法将:
(为了简单起见,我故意忽略对非整数或负数的检查;我将把它作为一个练习给你!)
创建一个isPrime.js文件:
module.exports = (number) => {
if (typeof number !== 'number') {
throw new TypeError('argument must be a number!');
}
if (number < 2) {
return false;
}
for (let i = 2; i < number; i++) {
if (number % i === 0) {
return false;
}
}
return true;
};
现在,对于该main.cpp文件,用下面的代码替换它的内容:
#include <nan.h>
NAN_METHOD(IsPrime) {
if (!info[0]->IsNumber()) {
Nan::ThrowTypeError("argument must be a number!");
return;
}
int number = (int) info[0]->NumberValue();
if (number < 2) {
info.GetReturnValue().Set(Nan::False());
return;
}
for (int i = 2; i < number; i++) {
if (number % i == 0) {
info.GetReturnValue().Set(Nan::False());
return;
}
}
info.GetReturnValue().Set(Nan::True());
}
NAN_MODULE_INIT(Initialize) {
NAN_EXPORT(target, IsPrime);
}
NODE_MODULE(addon, Initialize);
打开你的test.js文件,并用下面的代码交换现有的内容:
const {IsPrime} = require('./build/Release/addon'); // native c++
const isPrime = require('./isPrime'); // js
const number = 654188429; // thirty-fifth million first prime number (see https://primes.utm.edu/lists/small/millions/)
const NATIVE = 'native';
const JS = 'js';
console.time(NATIVE);
console.log(`${NATIVE}: checking whether ${number} is prime... ${IsPrime(number)}`);
console.timeEnd(NATIVE);
console.log('');
console.time(JS);
console.log(`${JS}: checking whether ${number} is prime... ${isPrime(number)}`);
console.timeEnd(JS);
重新通过npm run compile
编译一下我们的native c++ module,然后运行测试:
node test.js
native: checking whether 654188429 is prime... true
native: 1845.512ms
js: checking whether 654188429 is prime... true
js: 3177.400ms
可以看到,使用在我的mac上,C ++函数的平均时间为1800ms,JavaScript的时间为3100ms,导致算法的C ++版本比其JavaScript等效的速度快1.7倍。
在这篇文章中,我想告诉你Node.js平台是开放的本地,低级别的扩展。但是,编写这些过程需要一些准备和额外的工具。虽然本机代码确实可以更高性能,但您仍然应该更喜欢JavaScript解决方案,除非迫切需要进行重度优化或者访问低级API。