Nodejs 使用 C/C++ addons with Node-API

遇到一个需求,
需要用cpp写一个工具包,提供一系列功能函数(运算量大,js跑太慢)
但最终这些功能函数要由js调用。

看了几种js和cpp桥接的方案。

1.AssemblyScript是把ts转wasm的。
2.Emscripten可以把.cpp编译成.wasm,或者.js。
但折腾emcc指令老半天,试图在export functions里指明需要的功能函数,一直报错undefined export symbol
放弃

3.发现node自带一个cpp插件功能 C++ addons
官方文档 https://nodejs.org/api/addons.html

原理是把.cpp编译目标从.dll/.so换成.node,一个能跑在js引擎上的动态链接库。
实际runtime的时候,用process.dlopen() 加载执行。

偷别人一张图
在这里插入图片描述

用的时候
const pkg = requir('xx.node'); pkg.foo();
看上去比较像我们想要的。

由于工具链是js端的,用起来比cpp端的工具舒服多了。

官方推荐采用node-api方式,
推荐node-gyp和cmake。
我用node-gyp (generate your projects),
文档 https://nodejs.org/api/n-api.html#node-gyp

安裝node-addon-api

npm install -g node-addon-api

注意,windows下npm全局安裝的包,可能不能被node正确找到,报错Cannot find module node-addon-api
需要自行添加一个环境变量,连接到全局的node目录。
NODE_PATH='D:\\Program Files\\nodejs\\node_modules\\'
linux下应该没这个问题。

安装

npm install node-gyp -g

在项目根目录下新建一个binding.gyp文件,描述编译输入输出

{
  "targets": [
    {
      "target_name": "demo",
      "sources": [ "./src/main.cpp" ]
    }
  ]
}

创建描述文件

node-gyp configure

gyp info it worked if it ends with ok
gyp info using node-gyp@9.3.0
gyp info using node@16.17.0 | win32 | x64
gyp info find Python using Python version 3.10.7 found at "C:\Python310\python.exe"
gyp http GET https://nodejs.org/download/release/v16.17.0/node-v16.17.0-headers.tar.gz
gyp http 200 https://nodejs.org/download/release/v16.17.0/node-v16.17.0-headers.tar.gz
gyp http GET https://nodejs.org/download/release/v16.17.0/SHASUMS256.txt
gyp http GET https://nodejs.org/download/release/v16.17.0/win-arm64/node.lib
gyp http GET https://nodejs.org/download/release/v16.17.0/win-x64/node.lib
gyp http GET https://nodejs.org/download/release/v16.17.0/win-x86/node.lib
gyp http 200 https://nodejs.org/download/release/v16.17.0/SHASUMS256.txt
gyp http 200 https://nodejs.org/download/release/v16.17.0/win-x64/node.lib
gyp http 404 https://nodejs.org/download/release/v16.17.0/win-arm64/node.lib
gyp http 200 https://nodejs.org/download/release/v16.17.0/win-x86/node.lib
gyp info find VS using VS2019 (16.11.32228.343) found at:
gyp info find VS "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools"
gyp info find VS run with --verbose for detailed information
gyp info spawn C:\Python310\python.exe
gyp info spawn args [
gyp info spawn args   'D:\\Program Files\\nodejs\\node_modules\\node-gyp\\gyp\\gyp_main.py',
gyp info spawn args   'binding.gyp',
gyp info spawn args   '-f',
gyp info spawn args   'msvs',
gyp info spawn args   '-I',
gyp info spawn args   'E:\\Users\\Documents\\Projects\\test\\cal\\build\\config.gypi',
gyp info spawn args   '-I',
gyp info spawn args   'D:\\Program Files\\nodejs\\node_modules\\node-gyp\\addon.gypi',
gyp info spawn args   '-I',
gyp info spawn args   'C:\\Users\\Administrator\\AppData\\Local\\node-gyp\\Cache\\16.17.0\\include\\node\\common.gypi',
gyp info spawn args   '-Dlibrary=shared_library',
gyp info spawn args   '-Dvisibility=default',
gyp info spawn args   '-Dnode_root_dir=C:\\Users\\Administrator\\AppData\\Local\\node-gyp\\Cache\\16.17.0',
gyp info spawn args   '-Dnode_gyp_dir=D:\\Program Files\\nodejs\\node_modules\\node-gyp',
gyp info spawn args   '-Dnode_lib_file=C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\node-gyp\\\\Cache\\\\16.17.0\\\\<(target_arch)\\\\node.lib',
gyp info spawn args   '-Dmodule_root_dir=E:\\Users\\Documents\\Projects\\test\\cal',
gyp info spawn args   '-Dnode_engine=v8',
gyp info spawn args   '--depth=.',
gyp info spawn args   '--no-parallel',
gyp info spawn args   '--generator-output',
gyp info spawn args   'E:\\Users\\Documents\\Projects\\test\\cal\\build',
gyp info spawn args   '-Goutput_dir=.'
gyp info spawn args ]
gyp info ok

会下载一堆东西,比如node.lib,放在C:\\Users\\Administrator\\AppData\\Local\\node-gyp\\Cache\\16.17.0里。

构建

node-gyp build

自动读取当前根目录的binding.gyp

最后得到/build/Release/demo.node

const demo= requir('./build/Release/demo'); 

demo.foo();
// console打印 hello world

注意,node插件的版本互不兼容

用node14编译的.node无法在node16运行。
同样用node16编译的.node无法在node14运行。

确保编译版本和运行版本一致。

虽然nodejs官网自称可以跨版本使用。

This API will be Application Binary Interface (ABI) stable across versions of Node.js. It is intended to insulate addons from changes in the underlying JavaScript engine and allow modules compiled for one major version to run on later major versions of Node.js without recompilation. The ABI Stability guide provides a more in-depth explanation.

踩坑

如果遇到,报错信息 kernel32.lib(KERNEL32.dll) : fatal error LNK1103: 调试信息损坏;请重新编译模块
需要删除build目录,
重新在正确的node版本中,用node-gyp configure生成build

报错D:\Program Files\nodejs\node_modules\node-addon-api\napi.h(33,1): fatal error C1189: #error: Exception support not det ected. Define either NAPI_CPP_EXCEPTIONS or NAPI_DISABLE_CPP_EXCEPTIONS.

在bindings.gyp里加上
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]
在这里插入图片描述

报错 cannot find 'node_api.h' dependency of 'napi.h'
在我的电脑上位于
C:\Users\Administrator\AppData\Local\node-gyp\Cache\16.17.0\include\node
不知道自己电脑在哪的话
运行node-gyp configure
输出信息中有node-gyp的默认缓存路径。

報錯node-addon-api\napi-inl.h(749,1): error C2440: “<function-style-cast>”: 无法从“initia lizer list”转换为“T” [E:\Users\Documents\Projects\test\cal\build\demo.vcxproj]

进阶开发

node-addon-api官方api地址 https://github.com/nodejs/node-addon-api#api-documentation
(注意,和node-api这个库的语法不一样)

获取函数入参
Napi::Value MyObject::JsToCpp2DArray(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() <= 0 || !info[0].IsArray()) {
Napi::TypeError::New(env, “array expected”).ThrowAsJavaScriptException();
}

The Napi::CallbackInfo object contains the arguments passed by the caller. The number of arguments is returned by the Length method. Each individual argument can be accessed using the operator[] method.

入参类型判断
info[0].IsArray()
info[0].IsObject()

新建指定类型
string为例,有6个重载,无一例外都需要env。
在这里插入图片描述

js类型内部转换

info[0].As<Napi::Array>()

js类型与cpp类型互相转换

cpp转js,使用New构造函数

Napi::Env env;
int a = 12233; // cpp类型int
Napi::Number b = Napi::Number::New(env, result); // 转成js里的Number

js转cpp

//布尔
Napi::Boolean b;
bool cpp_bool = b.Value();
 
// 数值
Napi::Number nm;
int32_t i32 = nm.Int32Value();
int64_t i64= nm.Int64Value();
double db = nm.DoubleValue();

// 字符串
Napi::String str;
std::string cpp_str = str.Utf8Value();
std::u16string cpp_u16_str = str.Utf16Value();

// 枚举。枚举简单数据类型。
// 官方推荐使用Napi::External
// https://github.com/nodejs/node-addon-api/issues/1034
Napi::Object obj;
enum Class Color { red, green, blue};
Color *color_ptr = obj.Get("color").As<Napi::External<Color >>().Data(); // External.Data()返回指针。
//想得到正确的值可能得强制转换 
Color c = (Color)*color_ptr;// 但如果值不在枚举范围内,会发生不可控错误
// 这也不是C#,没有Enum.Parse 哭了。

//对象
// Napi::Object无法直接转换成cpp类型。
// 把每个字段自己取出来,一一赋值吧。
// 比如cpp中有一个Student类
// Napi::Object obj;
// Student stu = new Student();
// stu.name = obj.Get('name').Utf8Value(); // 一一赋值
// stu.age = obj.Get('age').Int64Value();


// 简单类型数组
template <typename T, uint32_t len> void CopyFromTypedArray(T* dst, Napi::TypedArray arr) {
  // 支持简单类型数组的复制
  for (uint32_t i = 0; i < len; ++i){ dst[i] = arr[i]; }
}
// 如果数组元素是复杂类型,需要手写一个复杂类型的复制函数


传递复杂数据结构
// https://blog.csdn.net/baidu20008/article/details/88736047

– 更新
强类型语言真的太令人胃痛了…
各种类型转换的接口写的人想死。

能看到这里的人,有力气的话还是去研究Emscripten吧。
我终于知道为啥NodeAPI不受欢迎了。

– 更新

写了一篇Emscripten+React的踩坑记录。
https://blog.csdn.net/w55100/article/details/127541744

虽然Emscripten支持的数据类型比Napi更少,但好处是可以脱离Node环境,直接运行在浏览器环境。
语法更简单,集成也比较方便。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值