文章参考:我是如何在electron上调用dll的导出函数的 - node-addon-api
一、背景与需求
在Electron项目中直接通过FFI库(如koffi)调用包含复杂C++标准库类型(如std::string
、std::vector
)的DLL时,手动构造结构体和函数原型效率低下。本文提供一种高效方案:通过C++编写Node原生模块,桥接Electron与业务DLL,适用于已有大型C++项目与Electron集成场景。
二、技术选型与参考
- 核心工具 :
- Node-API:用于构建跨版本Node模块的C接口
- node-addon-api :Node-API的C++封装库(GitHub)
- node-gyp:Node原生模块构建工具
- 参考文档 :
三、实现步骤
1. 环境准备
-
安装 Visual Studio(确保包含C++桌面开发组件)
-
安装 Python 3.x 和 node-gyp :
bashnpm install -g node-gyp
2. 创建Node模块项目
bash
mkdir electron-dll-bridge && cd electron-dll-bridge
npm init -y
npm install node-addon-api
3. 编写C++模块代码
module.cpp
cpp
#include <napi.h>
#include <Windows.h>
#include <string>
// 声明DLL导出函数(示例)
extern "C" __declspec(dllimport) int AddFunc(int a, int b);
extern "C" __declspec(dllimport) const char* StrAddFunc(const char* a, const char* b);
// 封装AddFunc
Napi::Number AddFuncWrapper(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
int a = info[0].As<Napi::Number>();
int b = info[1].As<Napi::Number>();
return Napi::Number::New(env, AddFunc(a, b));
}
// 封装StrAddFunc
Napi::String StrAddFuncWrapper(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::string a = info[0].As<Napi::String>();
std::string b = info[1].As<Napi::String>();
return Napi::String::New(env, StrAddFunc(a.c_str(), b.c_str()));
}
// 模块初始化
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("add", Napi::Function::New(env, AddFuncWrapper));
exports.Set("strAdd", Napi::Function::New(env, StrAddFuncWrapper));
return exports;
}
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
4. 配置构建文件
binding.gyp
python
{
"targets": [
{
"target_name": "bridge",
"sources": ["module.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
"libraries": ["$(ProjectDir)../libs/your_dll.lib"] # 指向DLL的LIB文件
}
]
}
5. 编译模块
bash
node-gyp configure
node-gyp build --arch=x64 --msvs_version=2022
编译产物:build/Release/bridge.node
四、Electron集成
1. 文件部署
- 将编译后的
bridge.node
和依赖的DLL文件(如your_dll.dll
)放置于Electron项目的native
目录 - 配置
package.json
确保打包包含原生模块:
json
{
"build": {
"extraResources": [
{ "from": "native/*", "to": "./" }
]
}
}
2. 在Electron中调用
typescript
import path from 'path';
import { app } from 'electron';
const nativeModule = require(path.join(app.getAppPath(), 'native', 'bridge.node'));
// 调用示例
console.log(nativeModule.add(3, 5)); // 输出: 8
console.log(nativeModule.strAdd("Hello", "World")); // 输出: HelloWorld
五、调试与打包优化
1. 调试配置
- 附加调试进程 :在Visual Studio中使用
调试 -> 附加到进程
,选择Electron主进程 - 断点设置 :确保调试版
.node
文件与Electron加载的模块路径一致
2. 自动更新模块
在Visual Studio的生成后事件中添加命令,自动复制新编译的模块:
bat
xcopy /Y "$(ProjectDir)build\Release\bridge.node" "$(SolutionDir)electron-app\native\"
3. 打包问题解决
-
问题:打包后DLL文件丢失
-
解决方案 :
- 在
electron-builder
配置中声明依赖文件:
json"extraFiles": [ "native/your_dll.dll" ]
- 运行时动态设置DLL搜索路径:
cpp#include <Windows.h> SetDllDirectory(L"native");
- 在
六、注意事项
- ABI兼容性:确保DLL与Node模块使用相同的运行时库(如MT/MD)
- 异常处理 :使用
Napi::Error::New().ThrowAsJavaScriptException()
传递错误 - 内存管理:避免在C++中分配需由JavaScript释放的内存