关于在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++ 插件的起始文件)

js 复制代码
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传递数据

cpp 复制代码
#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 源码

cpp 复制代码
#ifndef HELPER_H
#define HELPER_H

#include <string>

// 声明一个辅助函数
std::string greet(const std::string& name);

#endif

helper.cpp 源码

cpp 复制代码
#include "helper.h"

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

binding.gyp 基础配置

json 复制代码
{
  "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格式
cpp 复制代码
string name = info[0].As<Napi::String>().Utf8Value();

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

cpp 复制代码
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++工具等等,当我们解决了一个又一个自己认为很难的问题就说明我们一直在成长,作为一个计算机软件爱好者,也希望我和大家的路越走越远。

相关推荐
阿里小阿希28 分钟前
解决 pnpm dev 运行报错的坎坷历程
前端·node.js
未脱发程序员39 分钟前
分享一款开源的图片去重软件 ImageContrastTools,基于Electron和hash算法
前端·javascript·electron
geovindu1 小时前
vue3: pdf.js 2.16.105 using typescript
javascript·vue.js·typescript·pdf
视频砖家1 小时前
Web前端VSCode如何解决打开html页面中文乱码的问题(方法2)
前端·vscode·vscode乱码·vscode中文乱码·vscode中文编码
2401_837088501 小时前
CSS transition过渡属性
前端·css
我爱吃朱肉1 小时前
深入理解 CSS Flex 布局:代码实例解析
前端·css
喝养乐多长不高1 小时前
Spring Web MVC基础理论和使用
java·前端·后端·spring·mvc·springmvc
zh73143 小时前
支付宝沙盒模式商家转账经常出现 响应异常: 解包错误
前端·阿里云·php
ZHOU_WUYI3 小时前
用react实现一个简单的三页应用
前端·javascript·react.js
samroom3 小时前
Vue项目---懒加载的应用
前端·javascript·vue.js·性能优化