Electron 原生模块开发 & 调用实践指南

 本文首发同名微信公众号:前端徐徐  

大家好,我是徐徐。今天我们讲讲如何在 Electron 中进行原生模块开发以及相应的调用实践指南。

前言

在 Electron 应用开发的过程中,开发者常常会遇到一些标准 Node 环境 和 Electron API 无法直接解决的特殊场景。这些场景可能涉及系统底层交互、高性能计算、特定硬件接口访问等复杂需求。为了突破这些限制,开发者需要借助原生模块——即 .dll.dylib.so.node 文件——来扩展应用的功能边界。

原生模块为 Electron 应用提供了强大的扩展能力,使开发者能够:

1. 突破 JavaScript 的性能瓶颈,通过使用 C/C++、Rust 等高性能语言编写核心功能模块。

2. 直接访问底层系统硬件和操作系统接口,实现 JavaScript 难以直接完成的底层操作。

3. 集成现有的系统级库和第三方依赖,极大地扩展应用的功能可能性。

4. 优化计算密集型任务的执行效率,显著提升应用的整体性能表现。

然而,原生模块的开发和集成并非易事。它要求开发者具备跨平台编译、系统底层编程、性能优化等多方面的专业技能。本文将系统性地介绍 Electron 原生模块的开发流程、调用方法和最佳实践,旨在帮助开发者全面掌握原生模块开发的关键技能,为 Electron 应用赋能。

调用原生模块的技术选择

对于 Electron 调用原生模块,主要有以下技术选择:

  1. C/C++ 编写的 Node.js 扩展,node-gyp 编译构建。这种方式性能最高,但开发复杂度最大。
  2. FFI (Foreign Function Interface) 模式,使用 ffi-napiref-napiKoffi 等库。这种方式最为灵活,调用系统库最方便。
  3. Rust 编写的 Node.js 扩展,NAPI-RS 或者 Neon 编译构建。这种方式可以得到安全性和性能的平衡。
  4. WebAssembly 方式。这种方式可以有跨平台,多语言支持。
  5. N-API 模式。这种方式官方推荐,版本兼容性好。

对于上面提到的技术,每种技术都有自己的适用场景,可以根据自身的业务场景做相应的选择。第一种方式和最后一种方式都是最复杂的,而且对技术的要求相对较高,需要懂 C++ 相关的开发,环境也是最为复杂的,其余三种在环境和实践操作上更加容易上手,适合前端工程师。当然,用 Rust 开发的话也需要一定的要求,但是我个人觉得 Rust 的环境更为简单和方便,依赖较少。这篇文章主要是讲解第二种和第三种方式的调用,我也是在这两种方式中有相应的实践,所以就选择他们来做讲解。

FFI 调用原生模块

在早期,在 Electron 中 FFI 动态调用动态链接库(.dll, .so, .dylib)大部分都是使用

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

这个库,不过现在这个库已经不怎么维护了,而且随着 Electorn 的升级,

这个库的兼容性也越来越差(Error with Electron and ffi-napi · Issue #238 · node-ffi-napi/node-ffi-napi · GitHub),

还有一个点就是依赖环境可能会让你非常头疼,因为它严重依赖node-gyp 环境。我前期开发 Electorn 也使用的是这个库,

随着时间的推移后面发现了 koffi (Koffi)这个库,

简直就是救星,不管是从性能还是兼容性都有相当大的提升。下图是一个比较

基础使用

这里我们演示一下基础的使用,由点到面去了解如何使用 koffi。

构建dylib、dll文件

我本地的环境是OS X,我们为了演示方便,就以构建dylib为例子。我们写一个简单的 C 函数就可以做验证了,我们创建一个sum.c文件,内容如下

#include <stdint.h>

#if defined(WIN32) || defined(_WIN32)
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif

EXPORT uint64_t sum(int a,int b) {
    return a + b;
}

然后你可以执行

gcc -dynamiclib -undefined suppress -flat_namespace sum.c -o sum.dylib

这样就会构建一个sum.dylib文件,下面我们就可以愉快的调用它了。

当然如果你是 windows 的话,可以执行

cl.exe /D_USRDLL /D_WINDLL sum.c /link /DLL /OUT:sum.dll

node调用.dylib、.dll

我们先安装 koffi

yarn add koffi

然后就可以很愉快的使用了。在 src/main 下新建一个 native 的目录,添加 index.ts,koffi 使用非常简单,如下

import koffi from 'koffi'
import path from 'path'

const sumLib = koffi.load(path.resolve(
  __dirname,
  "../../resources/dylib/sum.dylib"
))

const dylibNativeSum = sumLib.func('__stdcall','sum','int',['int','int'])

export const dylibCallNativeSum = (a:number,b:number) => {
  return dylibNativeSum(a,b)
}

这里需要注意一个点,就是我们需要改动一下 config/vite/main.js 中 rollupOptions 的 external,把koffi导入包转成外部依赖,不然在构建运行的时候会报错。

rollupOptions: {
      external: [
        "electron",
        "sqlite3",
        "koffi",
        ...builtinModules,
      ],
      output: {
        entryFileNames: "[name].cjs",
      },
    },

更多的用法可以参考下面的文档,里面有非常多的用法,包括传值,注册回调等

Function calls | Koffi

Rust 编写的 Node.js 扩展

至于为什么要选择 Rust 实现,其实也不是为了学习 Rust 而 Rust,是因为在开发的过程中的确遇到了瓶颈,然后用 Rust 来处理了一些问题,其实也是多了一种选择,特别是处理耗时任务的时候,Rust 表现非常优异,当然他还有其他的优点,比如:内存安全、跨平台编译、零成本抽象、并发模型支持优秀等。下面我们就来尝试在 Electron 中来调用 Rust 构建的 node 包。

Rust是什么

两个链接告诉你,环境的搭建步骤也告诉你了:

入门 - Rust 程序设计语言

https://course.rs/about-book.html

只有把环境搭建好,才可以开始下面的步骤哦。

如何通过 Rust 构建 node 包

这里推荐两个框架:

两个框架的比较可以参考:Comparison with neon – NAPI-RS

我们在这里选择 NAPI-RS。

首先全局安装一下 @napi-rs/cli 脚手架

pnpm add -g @napi-rs/cli

然后用 napi new 创建一个新的项目,当然,如果你有成熟的分包管理工具也可以在原项目下创建项目,后面在打包构建的时候整合,我们这个为了方便简单演示其原理我们就新建一个项目。

这里我们就选择所有平台,简直就是跨端大杀器。

创建项目后会如下所示。

这里我们可以在 sum 的下面加一个减法的函数 subtraction ,测试一下它的易用性

#[napi]
pub fn subtraction(a: i32, b: i32) -> i32 {
  a - b
}

然后直接

pnpm run build

首次构建可能有点慢,但是后面就很快了。构建之后会出现一个你现在构建平台的一个 .node 文件。

我们将其拷贝至我们原有的 Electron 项目中的 resources/node 目录下。现在我们要来引用这个node文件,超级简单。

const rsNative = require(path.resolve(
  __dirname,
  "../../resources/node/rs-native.darwin-x64.node"
))

export const rsNativeSum = (a:number,b:number) => {
  return rsNative.sum(a,b)
}

export const rsNativeSubtraction = (a:number,b:number) => {
  return rsNative.subtraction(a,b)
}

是不是感觉上层调用非常方便,不用关心数据类型。到这里一个基础的 Rust 编写的 Node.js 扩展的例子就完成了,上手非常简单。

更加高级的应用就是编写一些 Rust 的应用程序,然后满足一些非常规的需求。

windows 相关的扩张可以参考:GitHub - microsoft/windows-rs: Rust for Windows

mac Os 相关的扩张可以参考:https://crates.io/categories/os::macos-apis

结语

我们这一节给大家展示了如何在 Electron 中开发原生模块以及一些基础调用方式,原生方法的扩展大大得扩展了 Electron 的应用场景,弥补了一些框架的局限性。当然在实际的开发中,我们需要注意一些三方 dll 或者 node 的兼容性以及安全性,做好容错相关的措施,不断的实践和调优才能创建出一个健壮的程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值