如何为Electron应用开发原生模块

前序

Electron 是目前流行的一种跨平台桌面应用开发框架。它结合了Chromium和Node.js,让前端开发者能够使用前端技术构建功能强大的桌面应用程序。

大家都知道JavaScript无论是在浏览器中运行、还是在Node.js中运行都是单线程运行的,所以并不适合在处理一些CPU密集型任务。但是Node.js允许开发者使用C、C++等语言开发像普通的Node.js模块一样通过require()函数加载的原生模块。因为Electron内置Node.js,这样就使得Electron同样具备了相同的能力。

在实际业务场景中,也有一些现成的C/C++项目,在Node.js项目中直接复用可以节约很多开发成本。

本文将探讨如何在Electron应用中开发原生模块,以扩展应用的功能和性能。

搭建原生模块开发环境

在目前的原生模块开发中,一般都是基于Node-Api进行开发。

Node-Api是什么呢?

Node-API(Node Application Programming Interface)是一个用于编写跨平台原生插件的封装层。它提供了一组稳定的 C/C++ 函数,使开发者可以编写与 Node.js 运行时环境兼容的原生插件。通过使用 Node-API,开发者可以消除由于 Node.js 版本变化而引起的插件不兼容的问题,并且能够更方便地编写和维护跨平台的原生模块。

理解一下就是我们无需为不同版本的Node.js编译不同版本的原生模块。不同版本的 Node.js 使用同样的接口为原生模块提供服务,这些接口是 ABI 化的,只要 ABI 的版本号一致,编译好的原生模块就可以直接使用,而不需要重新编译。

ABI 化是指将软件接口转化为应用程序二进制接口(Application Binary Interface)的过程。在编程中,ABI 化的目标是确保在不同编译器、操作系统以及硬件平台之间的二进制兼容性。通过将接口规范化为二进制标准,不同模块或程序可以相互调用和交互,而无需关心具体实现细节和底层平台差异。Node-API 的设计就是为了实现跨版本和跨平台的 ABI 兼容性,以便 C/C++ 模块能够在不同的 Node.js 环境中无需重新编译即可运行。

Node-API有哪些开发方式?

基于 Node-API 的原生模块开发可以使用 C 语言或者基于 node-addon-api 项目使用 C++ 语言的两种方式。

  1. 基于C语言开发:由于受众为前端开发者,C语言的编程复杂度高、开发效率较低,开发过程可能较为繁琐。

  2. 基于 node-addon-api项目开发 :相对于纯 C 语言开发,C++ 提供了更多的高级特性和工具,开发效率相对较高。node-addon-api 项目提供了一组方便的 C++ API 封装,简化了与 Node-API 的交互过程,减少了部分底层操作。

接下来我们就基于这个项目来开发一个 Electron 的原生模块。

  1. 安装 Node.js:首先,确保您已经安装了 Node.js,可以从 Node.js 官网下载并安装适合您操作系统的版本。

  2. 需要全局安装 node-gyp,它是专门为构建开发、编译原生模块环境而生的跨平台命令行工具。

    npm install -g node-gyp

  3. 创建空项目目录:创建一个新的项目目录,作为原生模块的开发目录。

  4. 初始化 npm 项目:进入项目目录,打开终端,并运行以下命令初始化 npm 项目。

csharp 复制代码
npm init
  1. 根据提示,设置项目的名称、版本等信息。

  2. 安装 node-addon-api:运行以下命令安装 node-addon-api 模块:

css 复制代码
npm install node-addon-api --save-dev

以上就已经搭建好了基本的原生模块开发环境,接下来通过一个简单的例子,来实现一个访问系统文件的原生模块带大家了解一下开发流程。

开发原生模块访问系统文件并读取文件内容

开发原生模块需要熟悉C++编程和Node.js的C/C++扩展开发,涉及一些底层的编程和跨平台的知识。这里的例子较为简单,方便大家理解。

  1. 创建 C++ 文件:在项目目录下创建一个 C++ 源文件,例如 filesystem.cpp,并添加以下代码内容:
cpp 复制代码
#include <napi.h>
#include <iostream>
#include <fstream> 

Napi::String ReadFile(const Napi::CallbackInfo& info) { 

    Napi::Env env = info.Env(); 

    // 读取文件路径参数 
    std::string filePath = info[0].As<Napi::String>().Utf8Value(); 

    // 打开文件并读取内容 std::ifstream file(filePath);
    std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();

    // 将内容转换为 Napi::String 返回 
    return Napi::String::New(env, content); 
} 

Napi::Object Init(Napi::Env env, Napi::Object exports) { 
    exports.Set("readFile", Napi::Function::New(env, ReadFile));
    return exports; 
}
NODE_API_MODULE(addon, Init)

上述代码定义了一个 ReadFile 函数,它接受一个文件路径参数,并返回文件的内容。

NODE_API_MODULE是Node-API 提供的一个重要的宏,用于在 C++ 中定义 Node.js 原生模块的入口点,创建一个模块初始化函数,并将此函数暴露给 Node.js 运行时。

使用 NODE_API_MODULE 定义原生模块的入口点,可以让开发者以 C++ 的方式编写模块的初始化、导出函数、属性等,并与 Node.js 运行时进行交互。并可以在 Node.js 中加载和使用。

Init 函数是模块的初始化函数,用于在模块加载时注册和导出相应的函数、属性等。然后,通过 NODE_API_MODULE 将 Init 函数暴露给 Node.js 运行时,并指定模块的名字为 "addon"。

  1. 创建 binding.gyp 文件:在项目目录中创建一个名为 binding.gyp 的文件,并添加以下内容:
json 复制代码
{ 
    "targets": [ 
        { 
            "target_name": "filesystem", 
            "sources": ["filesystem.cpp"]
        }
}

binding.gyp 是一个用于配置 Node.js 原生模块构建过程的项目文件。它采用了 JSON 格式,并使用特定的语法来定义编译选项、依赖项和源文件等信息。通过编辑 binding.gyp 文件,可以指定编译器和链接器的选项,添加所需的依赖库,并确定要编译的源文件。

编译原生模块

在构建编译原生模块时,需要使用 node-gyp ,它会读取 binding.gyp 文件并根据其内容进行编译操作。node-gyp 提供了一个简化的构建流程,使得开发人员能够轻松地配置和构建原生模块。

使用以下命令构建该模块。

ruby 复制代码
$ node-gyp configure 
$ node-gyp build

运行上述命令将生成一个名为 build/Release/filesystem.node 的编译好的原生模块文件。

接下来,就可以在任何 Node.js 文件中使用该模块:创建一个名为 app.js 的 JavaScript 文件,并添加以下代码

js 复制代码
const addon = require('./build/Release/filesystem.node'); 

const filePath = '/path/to/file.txt'; 

const content = addon.readFile(filePath); 

console.log(content);

上述代码使用 require 导入原生模块,然后调用 readFile 函数读取指定文件的内容,并输出到控制台。

总结

在 Electron 应用中,使用和开发原生模块可以为前端开发同学提供更广阔的可能性;能够利用操作系统级别的功能来提升应用的性能。

那么有同学就会有疑问,除了自己开发原生模块还有什么方案可以给Electron应用提供更广泛的功能扩展,包括底层系统功能的访问、高性能计算呢?

当然有,在日常开发中,可以使用动态链接库(Dynamic Link Library,DLL)进行扩展功能的模块化。使用动态链接库可以使用更多的语言和框架进行开发,适合不同开发者的需求。比如,一个go开发者也可以给我们提供一个动态链接库供Electron调用,也可以将go代码打包成不同平台的文件供其他平台调用,更适合独立功能各个平台使用的场景。原生模块则更专注于为 Node.js 和 Electron 应用程序提供特定功能的开发。

在实际应用中,可根据具体需求和开发团队的技术栈来选择合适的方式,结合动态链接库和原生模块来扩展 Electron 应用程序的功能。

参考链接

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。

关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

相关推荐
不可能片场4 小时前
Electron 发布进程被回收 用脱离进程组托住
前端·electron
月走乂山2 天前
DSH 桌面版用 DeepSeek 报 REQUEST_EXTENSION 的根因定位与修复
electron·node.js·插件系统·deepseek·bug排查·dsh·deepseek harness
mont evergreen2 天前
一封从外部世界寄来的信:二维码数据如何穿越三重门,抵达你的屏幕
electron·vue·事件
fellow993 天前
DSH Desktop:把 DeepSeek Harness 变成真正的桌面应用
electron·agent·ai编程·deepseek
CDwenhuohuo4 天前
electron pc项目打包成桌面端
前端·javascript·electron
不可能片场5 天前
命令行中文变问号 我用环境变量救了场
前端·electron
不可能片场5 天前
AI视频口型对齐 用音频驱动说话镜头
前端·electron
Zoom6 天前
【开源】7 天,我用 Rust 复刻了 FinalShell!Rhost v1.0.0 发布
rust·electron·shell
爱丶不疚7 天前
Electron: Sentry 都采集了什么?你知道吗
前端·electron