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

关于在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.kler.cn/a/581840.html

相关文章:

  • 【GPT入门】第6课 openai接口介绍与参数说明
  • 远程手机遥控开关原理及应用
  • git commit messege 模板设置 (规范化管理git)
  • 机器学习基础(4)
  • 清华同方国产电脑能改windows吗_清华同方国产系统改win7教程
  • 《PaddleOCR》—— OCR
  • Agentic RAG 详解 - 从头开始​​构建你自己的智能体系统
  • 【web】网页崩溃
  • Cannot resolve symbol ‘view‘ Androidstudio报错解决办法
  • json中文编码问题
  • python之左移右移位运算
  • 政安晨【零基础玩转各类开源AI项目】Wan 2.1 本地部署,基于ComfyUI运行,最强文生视频 图生视频,一键生成高质量影片
  • Python+DeepSeek:开启AI编程新次元——从自动化到智能创造的实战指南
  • 【LLM学习】1-NLP回顾+Pytorch复习
  • jmeter-md5加密
  • C++和标准库速成(一)——HelloWorld和名称空间
  • Dify Web 前端独立部署指南(与后端分离,独立部署)
  • CAMEL 环境配置及基于硅基流动API的Agent构建
  • 【最新】DeepSeek 实用集成工具有那些?
  • tomcat配置应用----server.xml文件具体配置