使用node-addon-api实现从c到nodejs模块全流程
目录
1 前言
2 安装nodejs
3 安装开发工具链
3.1 安装node-gyp
3.2 安装编译工具链(C/C++ 编译器)
4 初始化 Node.js 项目
4.1 创建项目目录
4.2 初始化 package.json
4.3 安装必要的库
5 编写代码
5.1 创建项目结构
5.2 编写动态库代码
5.3 编写 Node.js 原生模块代码
5.4 配置 binding.gyp
6 编译模块
7 测试模块
1 前言
我们的客户端是使用electron打包的前端程序,因为electron基于nodejs的,所以我们想实现先用c/c++实现拉流+内容解密+硬件解码+硬件格式转换的功能,然后让nodejs去调用c/c++的库,我们调研了下面3种方式:
- webassembly:因为webassembly是运行在沙箱环境,无法访问网络资源,所以这种方式不可用。
- node-ffi:这种方式不需要编写额外的c/c++代码,直接使用已经编译好的库就行,但我们验证发现使用node-ffi对node版本有要求,高于16的node版本无法使用,这违反了公司的安全规定。
- node-addon-api:这种方式需要对每一个暴露的接口重写一遍,这稍微麻烦,但好在性能比较好且对于node版本没有什么限制,我们最终使用这种方法。
作为一个前端小白,下面详细的记录下如何将一个c语言写的库使用node-addon-api封装为nodejs的模块,过程中遇到一些坑并最终解决,希望其他朋友可以参考下。感谢chatgpt给予的帮助,下面内容参考chatgpt的回答,且已经得到完全的验证。
2 安装nodejs
在我的debian环境下使用下面命令来安装nodejs
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo
apt-get install -y nodejs
3 安装开发工具链
3.1 安装node-gyp
sudo npm install -g node-gyp
3.2 安装编译工具链(C/C++ 编译器)
sudo apt-get install -y build-essential
4 初始化 Node.js 项目
4.1 创建项目目录
mkdir c2node_example
cd c2node_example
4.2 初始化 package.json
npm init -y
4.3 安装必要的库
npm install node-addon-api
5 编写代码
5.1 创建项目结构
创建以下文件和目录:
c2node_example
├── binding.gyp
├── index.js
├── native
│ ├── addon.cc
│ ├── example.c
│ └── example.h
└── package.json
5.2 编写动态库代码
创建 native/example.c 和 native/example.h:
native/example.h:
#ifndef EXAMPLE_H
#define EXAMPLE_H
#ifdef __cplusplus
extern "C" {
#endif
int add(int num1, int num2);
void say_hello(const char* name);
#ifdef __cplusplus
}
#endif
#endif
native/example.c:
#include <stdio.h>
#include "example.h"
int add(int num1, int num2) {
return num1 + num2;
}
void say_hello(const char* name) {
printf("hello,%s!\n", name);
}
生成动态库:
cd native
gcc -shared -fPIC -o libexample.so example.c
cd ..
5.3 编写 Node.js 原生模块代码
创建 native/addon.cc,用于封装 C 动态库。
native/addon.cc:
#define NAPI_DISABLE_CPP_EXCEPTIONS
#include <napi.h>
#include "example.h"
Napi::Value Add(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
int a = info[0].As<Napi::Number>().Int32Value();
int b = info[1].As<Napi::Number>().Int32Value();
int result = add(a, b);
return Napi::Number::New(env, result);
}
Napi::Value SayHello(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::string name = info[0].As<Napi::String>().Utf8Value();
say_hello(name.c_str());
return env.Null();
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("add", Napi::Function::New(env, Add));
exports.Set("sayHello", Napi::Function::New(env, SayHello));
return exports;
}
NODE_API_MODULE(addon, Init)
5.4 配置 binding.gyp
binding.gyp
{
"targets": [
{
"target_name": "addon",
"sources": ["native/addon.cc"],
"include_dirs": ["./node_modules/node-addon-api"],
"libraries": ["-L../native", "-lexample"],
"cflags": ["-fno-exceptions"],
"xcode_settings": {
"OTHER_CFLAGS": ["-fexceptions"]
}
}
]
}
这里遇到了一个坑,可就是libraries的配置,不能写成-L./native,因为会在build目录下执行相应的命令,这个花费了很长时间才解决。
6 编译模块
在项目根目录运行以下命令:
npx node-gyp configure
npx node-gyp build
生成的模块文件位于 build/Release/addon.node
7 测试模块
编写 index.js:
const addon = require('./build/Release/addon');
console.log("5 + 3 =", addon.add(5, 3)); // 输出: 5 + 3 = 8
addon.sayHello("Node.js"); // 输出: Hello, Node.js!
运行测试: