使用electronc框架调用dll动态链接库流程和避坑

前情提要

需求背景:需要构建一个读卡的客户端应用,需要使用electronc框架+dll动态链接库。因为eletronc本身不能和硬件进行交互,需要通过dll文件完成和硬件的交互操作。

前提:需要确认你用到的dll文件信息

  • 语言(C/C++、C#)---对应使用FFI相关库、electron-edge-js
  • 位数(X32、X64)---运行的electronc框架以及最终打包架构必须与该位数相同
  • 具体函数信息(名称、作用、入参、出参)---决定你如何调用

本文将基于X64的dll文件及ffi相关库出一期调用dll文件的教程,主要说明下载依赖到最终打包的过程中可能出现的一些问题。

正文开始--------------

创建项目

首先快速创建一个electron-vite 项目,electron-vite构建工具类似于vue的脚手架,已经把主流程需要的东西配置好了,只需要根据具体业务需求往上边加东西即可。

具体的项目创建流程跟着官网即可快速开始 | electron-vite

注意:electron-vite 需要 Node.js 版本 20.19+, 22.12+ 和 Vite 版本 5.0+

项目创建好了,运行npm run dev 成功即可,这个页面被我改过了哟。

配置依赖所需环境

查询ffi相关库,发现ffi-napi已经长期未维护,适用于版本较低的旧项目,且容易出现兼容性问题,所以我们采用@napi-ffi/ffi-napi依赖,这个是基于前者新拉的分支,支持前者的api并且对node版本20+的兼容性更好。在下载依赖之前,需要进行前置的环境配置。

GitHub - napi-ffi/node-ffi-napi: A foreign function interface (FFI) for Node.js, N-API style

点击如图链接来到需要配置的环境(一定要注意版本的兼容),你需要准备好(以下是我的版本参考):

  • Python 3.12.6 (记得勾选配置环境变量)
  • Visual Studio 2022 (推荐直接在官网下载)
  • node-gyp 版本10及以上

GitHub - nodejs/node-gyp: Node.js native addon build tool

将环境安装好下载依赖即可

npm install @napi-ffi/ffi-napi

调用dll文件

  1. 首先准备好dll文件,放进项目的根目录中,该文件内包含一个示例函数:add: [ 'int', [ 'int', 'int' ] ]
  1. 创建一个dll调用js文件,写调用函数,引入到主进程(直接写在主进程也是可以的)

引入dll文件函数时,要注意函数名和数据类型的映射,要把c语言的数据类型转化成js的数据类型,具体的数据类型映射可以去ffi官网查找,也可以直接让ai帮忙转化,如果出现指针数据类型的情况,需要注意内存空间是否正确。

javascript 复制代码
import { Library } from '@napi-ffi/ffi-napi'
import path from 'path'

// DLL 放在项目根目录
const dllAbsolutePath = path.join(__dirname, '../../mydll.dll')
console.log('绝对路径', dllAbsolutePath)

// 加载 DLL 并定义函数签名
const myDll = Library(dllAbsolutePath, {
  add: ['int', ['int', 'int']] // 格式:[返回值类型, [参数1类型, 参数2类型]]
})

/**
 * 封装 DLL 的 add 函数调用
 * @param {number} num1 - 第一个整数参数
 * @param {number} num2 - 第二个整数参数
 * @returns {number} 两数之和
 * @throws {Error} 调用失败时抛出异常
 */
export function callDllAdd(num1, num2) {
  try {
    // 调用 DLL 中的 add 函数
    const result = myDll.add(num1, num2)
    console.log(`DLL add 调用成功:${num1} + ${num2} = ${result}`)
    return result
  } catch (error) {
    console.error('DLL add 调用失败:', error.message)
    throw new Error(`DLL 调用异常:${error.message}`)
  }
}
javascript 复制代码
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import { callDllAdd } from './dllCaller'
function createWindow() {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false
    }
  })

  mainWindow.on('ready-to-show', () => {
    mainWindow.show()
  })

  mainWindow.webContents.setWindowOpenHandler((details) => {
    shell.openExternal(details.url)
    return { action: 'deny' }
  })

  // HMR for renderer base on electron-vite cli.
  // Load the remote URL for development or the local html file for production.
  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
    mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
  } else {
    mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
  }
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
  // Set app user model id for windows
  electronApp.setAppUserModelId('com.electron')

  // Default open or close DevTools by F12 in development
  // and ignore CommandOrControl + R in production.
  // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
  app.on('browser-window-created', (_, window) => {
    optimizer.watchWindowShortcuts(window)
  })

  // IPC test
  ipcMain.on('ping', () => console.log('pong'))
  // 接收渲染进程的 DLL 调用请求
  ipcMain.handle('dll:add', async (_, num1, num2) => {
    // 调用封装好的 DLL 函数
    return callDllAdd(num1, num2);
  });


  createWindow()

  app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
复制代码
<script setup>
import { ref } from 'vue';
const { ipcRenderer } = window.electron || {}
const result = ref('');
async function testDllAdd() {
  try {
    const sum = await ipcRenderer.invoke('dll:add', 10, 20);
    result.value = sum;
  } catch (error) {
    console.error('渲染进程调用 DLL 失败:', error.message);
    result.value = `调用失败:${error.message}`;
  }
}
testDllAdd()
</script>

<template>
  <div style="color: black;">哈哈哈我是测试的----{{ result }}</div>
</template>

将代码写好再次运行npm run dev

出现报错Error: External buffers are not allowed

解决buffers报错

这里博主也是被卡了很久,找了很多资料(抓狂流泪...)

经查阅,这个是由于高版本electronc出于安全策略考虑,已不支持外部缓冲区(External buffers),但是ffi库需要依赖buffers才能运行,所以需要降低版本至20.3.8。

资料参考Electron 和 V8 内存笼 | Electron 框架

  1. 卸载当前 Electron 版本

npm uninstall electron --save-dev

  1. 安装 20.3.8 版本

npm install electron@20.3.8 --save-dev

  1. 重建原生模块(适配新的 Electron 版本)

npx electron-rebuild

  1. 重新运行

npm run dev

此时已可以成功调用dll函数了!!

提醒:可以在package.lock文件锁定版本,下次下载依赖时就不会自动更新了

打包

到这里如果直接打包运行exe文件,会出现报错说找不到dll文件资源,因为前面我们把dll文件放在根目录下,打包时没有自动复制到包里,导致报错,所以我们需要新增以下配置,让dll文件打包时能自动复制到安装包中。

dll文件路径指向使用electronc提供的通用变量,避免打包前/后的路径不同。

配置完后执行命令编译--打包--安装,运行应用即可得到一个调用了dll文件的客户端应用。(撒花!)

npm run build

npx electron-builder


以上是全部内容,欢迎指正!

相关推荐
weixin199701080162 小时前
【性能提升300%】仿1688首页的Webpack优化全记录
前端·webpack·node.js
冰暮流星2 小时前
javascript之数组
java·前端·javascript
晚霞的不甘2 小时前
Flutter for OpenHarmony天气卡片应用:用枚举与动画打造沉浸式多城市天气浏览体验
前端·flutter·云原生·前端框架
weixin79893765432...3 小时前
Vue 渲染体系“三件套”(template 模板语法、h 函数和 JSX 语法)
vue.js·h函数·template 模板·jsx 语法
xkxnq3 小时前
第五阶段:Vue3核心深度深挖(第74天)(Vue3计算属性进阶)
前端·javascript·vue.js
三小河3 小时前
Agent Skill与Rules的区别——以Cursor为例
前端·javascript·后端
Hilaku3 小时前
不要在简历上写精通 Vue3?来自面试官的真实劝退
前端·javascript·vue.js
三小河3 小时前
前端视角详解 Agent Skill
前端·javascript·后端
Aniugel3 小时前
单点登录(SSO)系统
前端