使用nodejs调用c++的方法很简单,网上也有很多教程,但是为什么公司一般不会大规模去使用c++扩展呢。
nodejs使用c++扩展的优缺点
优点:
c++作为编译型语言,是执行速度最快的编程语言之一,使用c++扩展可以提高代码的运行效率
c++拥有丰富的开源库,如果有些功能在nodejs中找不到合适的库,可以使用c++的库
缺点:
- 开发难度高、调试麻烦、维护成本高,并且跨平台时要编译多个版本
Napi将nodejs数据结构和c++数据结构相互转换是比较消耗性能的,所以使用c++扩展通常会比直接使用nodejs还要慢
当然,还是有一些程序适合使用c++扩展来写的,比如一些输入输出极其简单,但是计算过程却很复杂的程序。
比如nodjes的核心库 crypto,通过查看源码可以发现如下代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const { getFipsCrypto, setFipsCrypto, timingSafeEqual, } = internalBinding('crypto');
const { Hash: _Hash, HashJob, Hmac: _Hmac, kCryptoJobAsync, } = internalBinding('crypto');
|
相关文件都有这个 internalBinding('crypto'),这个就是说明是使用c++扩展实现的相关功能
实际上nodejs是使用了c++的OpenSSL库
如何实现一个简单的c++扩展
环境安装
首先需安装 node-gyp
npm install -g node-gyp, 请注意,你电脑需要有python环境,看源代码仓库就知道,它主要是python写的
需要配置你机器系统对应的c++编译环境
安装nodejs库 node-addon-api 和 bindings。(ps: 当然还有nan、原生的Node-API等,这里用的是最简单的node-addon-api)
编译一个hello world
1. 编写binding.gyp
具体的参数比较复杂,有专门的文档,这里知道 target_name 和 sources 就行了。
- target_name:是你编译后文件的文件,以
.node结尾,在这里编译后的文件就为hello.node
- sources: 是原始c++代码文件存放的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "targets": [ { "target_name": "hello", "cflags!": ["-fno-exceptions"], # -fno-exceptions 忽略掉编译过程中的一些报错 "cflags_cc!": ["-fno-exceptions"], "sources": ["addon/hello.cc"], "include_dirs": [ # 头文件搜索路径,这样通过#include <napi.h>就可以引入napi "<!@(node -p \"require('node-addon-api').include\")" ], "defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"], } ] }
|
2.编写c++代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <napi.h>
namespace __node_addon_api_hello__ {
Napi::String Hello(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); return Napi::String::New(env, "hello-world"); }
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "hello"), Napi::Function::New(env, Hello));
return exports; }
NODE_API_MODULE(addon, Init)
}
|
3.编写测试文件
1 2 3 4
|
const hello = require('bindings')('hello') console.log(hello.hello())
|
注意这里的 .hello()的函数名,对应c++文件中 Napi::String::New(env, "hello")
4. 执行
1 2
| node-gyp rebuild node test.js
|
执行 node test-hello.js 会输出 hello-world
至此,一个简单的nodejs调用c++的demo就完成了,代码我会放到文章的最后面
使用buffer共享内存
前面说过,nodejs和c++的数据类型相互转换其实是很耗费时间的,那有没有其他方案呢?
答案确实有:
- 一是我偶然想到的,因为工作中经常使用
nodejs操作大批量数据,经常不是容器 OOM 了,就是 nodejs OOM了。所以有时候会把大量数据以某种格式直接存到磁盘文件上,需要的时候按需读取。那么nodejs和c++是不是也可以这样交互数据呢,但是不确定 JSON 序列化和反序列化的开销会不会大于c++优化的性能。
- 还有一种更好的方案,使用 Buffer ! Buffer 是二进制数据,无需数据类型转换,而且
nodejs 创建的Buffer 受 v8 管理但是使用的是堆外内存。
那么下面也写个如和用buffer传递数据的小demo:
c++代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <napi.h>
namespace __node_addon_api_buffer { void UseBuffer(const Napi::CallbackInfo &info) { Napi::Env env = info.Env();
Napi::Object bufferObj = info[0].As<Napi::Object>(); int rot = info[1].As<Napi::Number>().Uint32Value();
void *data; size_t length; napi_status status = napi_get_buffer_info(env, bufferObj, &data, &length);
if (status != napi_ok) { Napi::TypeError::New(env, "Failed to get buffer info").ThrowAsJavaScriptException(); return; }
unsigned char *buffer = static_cast<unsigned char *>(data); for (size_t i = 0; i < length; i++) { buffer[i] += rot; } }
Napi::Object Initialize(Napi::Env env, Napi::Object exports) { exports.Set(Napi::String::New(env, "useBuffer"), Napi::Function::New(env, UseBuffer)); return exports; }
NODE_API_MODULE(addon, Initialize) }
|
(以上代码是ChatGPT生成的自己调了一下 ^.^)
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "targets": [ { "target_name": "buffer-test", "cflags!": ["-fno-exceptions"], # -fno-exceptions 忽略掉编译过程中的一些报错 "cflags_cc!": ["-fno-exceptions"], "sources": ["addon/buffer.cc"], "include_dirs": [ # 头文件搜索路径,这样通过#include <napi.h>就可以引入napi "<!@(node -p \"require('node-addon-api').include\")" ], "defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"], }, ] }
|
nodejs代码
1 2 3 4 5 6 7 8 9 10 11
|
const buffer = require('bindings')('buffer-test')
const nb = Buffer.from([1, 2, 3, 4, 5]);
console.log(nb) buffer.useBuffer(nb, 12); console.log(nb)
|
执行
1 2 3 4
| node test-buffer.js
<Buffer 01 02 03 04 05> <Buffer 0d 0e 0f 10 11>
|
我成功修改了Buffer中的数据
demo代码地址:https://github.com/ruomuc/practice/tree/master/napi-test
参考链接:
https://zhuanlan.zhihu.com/p/584943566
https://blog.risingstack.com/using-buffers-node-js-c-plus-plus/