万字长文,Electron高性能DLL调用实战:手把手教你使用Koffi调用DLL

目录

前言

Koffi VS 传统 ffi

Node.js microbenchmark实测性能对比

在 Electron 中使用 Koffi

一、安装 Koffi

二、加载 DLL

三、定义函数

1. 有返回值

2. 无返回值

3. 参数传递

四、处理复杂数据类型

1. 结构体参数值传递

2. 结构体参数指针传递

3. 通过结构体指针参数从 DLL 获取输出结果

3.1 宏定义

3.2 枚举

3.3 结构体 SingleDeviceState

3.4 结构体 DeviceState

3.5 结构体 VisionState

3.6 函数定义

3.7 定义结构体

3.8 定义函数签名

3.9 调用函数

五、注册和使用回调函数

1. 头文件

2. 定义特殊结构体

3. 注册回调函数

4. 在回调函数中解析指针

5. 为什么要这样做?

6. 这样做的好处


前言

最近在使用Electron开发一个工业视觉检测客户端的过程中,遇到了一个关键的技术需求:需要实现高效的图像处理算法。由于项目对处理速度要求极高,经过技术评估后,我们决定采用调用DLL(动态链接库)的方式来实现核心图像处理功能。

目前常用的 Node 原生 FFI 库主要包括:

  • 🟩 Koffi(较新,现代化设计)

  • 🟦 ffi-napi(经典方案)

  • 🟨 node-ffi(老旧版本,已弃用)

  • 🟧 其他如 edge-js(C#/CLR方向)、node-ffi-async(小众)

下面我将从多个维度详细对比 koffi 与传统 ffi 系列库👇

Koffi VS 传统 ffi

维度 koffi ffi-napi / node-ffi
开发者维护 ✅ 活跃维护,2024 年仍频繁更新 ⚠️ ffi-napi 基本停更,node-ffi 已弃用
性能 🚀 高性能(接近原生调用):零拷贝、无 JS Proxy 反射 🐢 较慢,内部依赖 libffi + 数据封装
安装难度 ✅ 纯 JavaScript,无需 node-gyp 编译 ❌ 需编译 C++ 模块,安装慢且容易报错
兼容性 ✅ 支持 Node ≥ 16 / Electron ≥ 20,Windows/Linux/macOS 全支持 ⚠️ 对新版 Electron/Node ABI 不兼容,常需 rebuild
API 设计 🧩 现代化、类型安全、链式 API,结构体定义清晰 🧱 接口古老,类型定义繁琐,缺乏类型检查
依赖管理 ✅ 无外部依赖(纯 JS) ❌ 依赖 ref-napi, ref-struct-napi, ref-array-napi 等多个子包
性能损耗 最小(接近 C 调用) 高(多层反射、内存复制)
安全性 ✅ 强类型封装 + 参数验证,减少崩溃风险 ❌ 用户容易传错参数导致崩溃
多线程支持 ✅ 原生线程安全,可在 Worker / 子进程使用 ⚠️ 线程隔离不完善,易出错
内存管理 ✅ 自动管理 + 明确的 Buffer 生命周期 ❌ 容易内存泄漏或访问非法内存
结构体与枚举支持 ✅ 直接定义 struct/enum,无需额外包 ❌ 需借助 ref-struct-napi 等外部包
函数回调支持(C→JS) ✅ 稳定,性能好 ⚠️ 支持不完整,且在 Electron 中常崩溃
跨平台 ABI 支持 ✅ 可直接解析 DLL / SO / dylib ⚠️ 部分系统(尤其 macOS ARM)需要 rebuild
生态与活跃度 📈 新兴但发展快 📉 老旧库逐渐退出主流
与 Electron ABI 兼容性 ✅ 完全兼容,无需 rebuild ❌ 经常在打包后崩溃(asrUnpack、ABI mismatch)
打包体积 纯 JS 几十 KB 含本地模块,上 MB
发布后可移植性 ✅ 可直接拷贝 DLL 使用 ❌ 不同 Node 版本需重新编译
调试与错误堆栈 ✅ JS 层清晰,报错可追踪 ❌ 原生崩溃难以定位(Segfault)

Node.js microbenchmark实测性能对比

测试项 ffi-napi koffi
调用 100 万次 C 函数(int add(int,int)) ~420ms ~80ms(快约 5 倍)
调用带结构体参数的函数 慢,需手动封装 Buffer 快约 3~6 倍
创建 / 销毁函数句柄 有延迟,C++ 层创建 即时,无编译依赖

👉 原因:Koffi 采用直接调用 V8 内部 TypedArray → C 层桥接,省去了 ffi-napi 复杂的反射层。

基于项目实际需求和技术分析,我们最终采用了Koffi库。


在 Electron 中使用 Koffi

需要说明的是,Koffi官方文档的表述较为晦涩,我们在开发过程中进行了大量探索性尝试,甚至某些功能的实现方案连AI都给出了错误建议。下文将结合C++头文件定义,详细讲解如何通过Koffi实现函数调用和回调注册等复杂操作,重点解析复杂结构体的处理方法。同时,还将介绍一些高级用法,其复杂程度已经超出当前AI的代码生成能力(包括ChatGPT都无法生成正确代码的情况)。本文特别适合对C++不是太了解,且没有接触过Koffi的同学。

特别提醒:在实际开发中,务必获取对应的dll头文件,并严格参照其中的各类定义进行操作。

一、安装 Koffi

确保 Electron 项目已初始化,运行以下命令安装 Koffi:

npm install koffi

或使用 Yarn:

yarn add koffi

二、加载 DLL

假设有一个名为 control.dll 的动态链接库,加载方法如下: 

const koffi = require('koffi');
const path = require('path');

// 加载 DLL 文件
const dllPath = path.join(__dirname, 'control.dll');
const lib = koffi.load(dllPath);

DLL文件加载完成后,会返回一个句柄对象lib,后续所有操作都需要通过这个lib对象来进行。

三、定义函数

1. 有返回值

假设C++的头文件中有如下函数定义,该函数有返回值

/// @brief 控制初始化
/// @return 返回状态
CONTROL_EXP ControlStatus Function_Export_Mode Control_Init();

在 Javascrip 中调用方式如下:

// 定义函数, 有多种写法, 可以这样写
const initFunc = lib.func('int Control_Init()');
//或者
const initFunc = lib.func('Control_Init', 'int', [])

// 调用函数
const statu
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光芒万丈向远方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值