当前位置: 首页 > news >正文

关于在electron(Nodejs)中使用 Napi 的简单记录

当我们使用electron想要集成一个C++ SDK实现很底层的算法逻辑就有可能与C++ SDK进行数据通信。
Napi 应该是比较好的选择,因为C++本身的运行速度很快,使用Napi也能很大程度上保证软件的兼容性、又不会阻塞C++线程、还可以很简单的与C++ 实现数据传递。

开始使用

  1. 安装 C++ 运行环境 直接装一个 VS2022 (c++桌面端开发)
  2. 安装 python 3.8.2 (如果开发electron win32 最好装32位环境)
  3. npm install -g node-gyp
  4. 新建一个文件夹 addon (存放demo文件)
  5. addon内执行npm init -y
  6. addon内执行npm install node-addon-api
  7. 新建 src/addon.cpp (相当于Napi的入口文件)(在下方)
  8. 新建 binding.gyp 并配置(在下方)

构建好后只需要 build/Release/addon.node 其他的可以删除(如果没有其他 dll 依赖)

我的Demo目录结构

在这里插入图片描述

app.js源码 (也就是Nodejs 集成C++ 插件的起始文件)

const addon = require("./build/Release/addon");function callback(data) {console.log("接收到C++传出: ", data);
}console.log(addon.sayHello("Hello C++"));// 传入回调函数
console.log("传入回调函数结果: ", addon.startSendingData(callback));

addon.cpp 源码 实现接收JS 传入的参数和回调函数 并使用JS传入的回调函数给JS传递数据

#include <napi.h>#include <thread>#include "helper.h"using namespace std;// 线程安全的回调
Napi::ThreadSafeFunction tsfn;
Napi::Function jsCallback;// 这个函数用于在后台线程中运行
void BackgroundThread(Napi::ThreadSafeFunction tsfn) {// 在 JS 线程调用回调tsfn.BlockingCall([](Napi::Env env, Napi::Function jsCallback) {jsCallback.Call({Napi::String::New(env, "Hello Nodejs")});});// 释放线程安全函数tsfn.Release();
}// 启动数据发送的函数
Napi::Value StartSendingData(const Napi::CallbackInfo& info) {Napi::Env env = info.Env();// 检查参数,确保传入的是一个回调函数if (!info[0].IsFunction()) {Napi::TypeError::New(env, "C++ StartSendingData 回调异常").ThrowAsJavaScriptException();return env.Undefined();}jsCallback = info[0].As<Napi::Function>();// 创建线程安全的 JS 回调tsfn = Napi::ThreadSafeFunction::New(env, jsCallback, "ThreadSafeFunction", 0, 1);// 启动后台线程,传递线程安全的回调thread(BackgroundThread, tsfn).detach();return Napi::Boolean::New(env, true);
}// 测试传参输出的函数
Napi::String SayHello(const Napi::CallbackInfo& info) {Napi::Env env = info.Env();// 获取传入的名称参数string name = info[0].As<Napi::String>().Utf8Value();// 调用外部的 greet 函数string message = greet(name);return Napi::String::New(env, message);
}// 定义模块并导出方法
Napi::Object Init(Napi::Env env, Napi::Object exports) {exports.Set("startSendingData", Napi::Function::New(env, StartSendingData));exports.Set("sayHello", Napi::Function::New(env, SayHello));return exports;
}// 绑定模块
NODE_API_MODULE(addon, Init);

helper.h 源码

#ifndef HELPER_H
#define HELPER_H#include <string>// 声明一个辅助函数
std::string greet(const std::string& name);#endif

helper.cpp 源码

#include "helper.h"std::string greet(const std::string& name) {return "接收到NodeJs传入: " + name;
}

binding.gyp 基础配置

{"variables": {"product_dir": "<(module_root_dir)/build/Release"},"targets": [{"target_name": "addon","sources": ["src/addon.cpp","src/helper.cpp"],"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")","include"],"libraries": [#"<(module_root_dir)/lib/agan_engine.lib"],"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],"cflags!": ["-fno-exceptions"],"cflags_cc!": ["-fno-exceptions"],"defines": ["NAPI_CPP_EXCEPTIONS"],}]
}

环境、目录和源码都配置粘贴好之后

  1. 执行 node-gyp rebuild
    在这里插入图片描述
  2. 运行 node ./app.js
    在这里插入图片描述
    注意:当nodejs 调用 build/Release/addon.node文件时传入的字符串在addon.cpp内接收时要转换为UTF8格式
string name = info[0].As<Napi::String>().Utf8Value();

上述Demo只是实现了一个很简单的Nodejs与C++实现数据交互的逻辑,在实际运用中可能会引入头文件(放到 include内) 还有dll、llib文件(放到lib目录内) 打包后如果缺失dll文件就会报错。又或是在C++线程回调中执行Nodejs传入的回调函数并传入C++执行结果,所以还需要在实践中一点一点积累经验。

void onCallStateChange(CALL_STATUS status) override {int* data = new int(status);tsfn.BlockingCall(data, [](Napi::Env env, Napi::Function callback, int* data) {callback.Call({Napi::Number::New(env, *data)});delete data;});}

我们看上述代码 重写了基类的虚函数,并使用 status 参数初始化了一块堆内存,然后将这个指针传给了线程安全函数,为什么不用栈内存? 是因为 onCallStateChange 执行结束后会销毁栈内存 导致空指针,所以使用堆内存是比较简单的做法。

结束!!!

做electron时间长了会发现我们要面对计算机的各种各样的功能和场景,而不仅仅是写前端页面和单纯的去了解electron,有时还要借助AI 使用C++、Python、Go等等实现一些前端触及不到的功能逻辑,使用第三方封装好的C++工具等等,当我们解决了一个又一个自己认为很难的问题就说明我们一直在成长,作为一个计算机软件爱好者,也希望我和大家的路越走越远。


http://www.mrgr.cn/news/93893.html

相关文章:

  • 多模态融合的分类、跨模态对齐的方法
  • 练习:关于静态路由,手工汇总,路由黑洞,缺省路由相关
  • vue3 + xlsx 实现导入导出表格,导出动态获取表头和数据
  • Linux 离线部署Ollama和DeepSeek-r1模型
  • 零基础掌握Linux SCP命令:5分钟实现高效文件传输,小白必看!
  • IO学习---->线程
  • QT系列教程(20) Qt 项目视图便捷类
  • 『PostgreSQL』PGSQL备份与还原实操指南
  • 【测试框架篇】单元测试框架pytest(4):assert断言详解
  • 【Linux内核系列】:深入理解缓冲区
  • 《平面几何强化训练题集》第2章5到9题
  • [GHCTF 2025]SQL??? 【sqlite注入】
  • uniapp+Vue3 开发小程序的下载文件功能
  • 选择排序算法OpenMP并行优化
  • 从新手到专家:嵌入式代码空间优化技巧
  • 【组件安装】Rocky 8.10 安装Local License Server 25.03.0 for Linux
  • C/C++中使用CopyFile、CopyFileEx原理、用法、区别及分别在哪些场景使用
  • 从零构建逻辑回归: sklearn 与自定义实现对比
  • [数据结构]并查集--C++版本的实现代码
  • sparkTTS window 安装