问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

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

创作时间:
作者:
@小白创作中心

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

引用
CSDN
1.
https://m.blog.csdn.net/github_39132491/article/details/144817099

在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-napi、ref-napi、Koffi等库。这种方式最为灵活,调用系统库最方便。
  3. Rust编写的Node.js扩展,NAPI-RS或者Neon编译构建。这种方式可以得到安全性和性能的平衡。
  4. WebAssembly方式。这种方式可以有跨平台,多语言支持。
  5. N-API模式。这种方式官方推荐,版本兼容性好。

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

FFI调用原生模块

在早期,在Electron中FFI动态调用动态链接库(.dll, .so, .dylib)大部分都是使用node-ffi-napi这个库,不过现在这个库已经不怎么维护了,而且随着Electron的升级,这个库的兼容性也越来越差。还有一个点就是依赖环境可能会让你非常头疼,因为它严重依赖node-gyp环境。后来发现koffi这个库,简直就是救星,不管是从性能还是兼容性都有相当大的提升。


基础使用

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

构建dylib、dll文件

我们以构建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",
  },
}

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

Rust编写的Node.js扩展

Rust是什么

Rust是一种系统编程语言,具有内存安全、跨平台编译、零成本抽象、并发模型支持优秀等特点。Rust的环境搭建可以参考以下链接:

如何通过Rust构建node包

这里推荐两个框架:

  • NAPI-RS(Home – NAPI-RS)
  • NEON-RS(Neon - Electrify Node.js with the power of Rust! | Neon)

两个框架的比较可以参考: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的兼容性以及安全性,做好容错相关的措施,不断的实践和调优才能创建出一个健壮的程序。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号