|
前序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(NodeApplicationProgrammingInterface)是一个用于编写跨平台原生插件的封装层。它提供了一组稳定的C/C++函数,使开发者可以编写与Node.js运行时环境兼容的原生插件。通过使用Node-API,开发者可以消除由于Node.js版本变化而引起的插件不兼容的问题,并且能够更方便地编写和维护跨平台的原生模块。理解一下就是我们无需为不同版本的Node.js编译不同版本的原生模块。不同版本的Node.js使用同样的接口为原生模块提供服务,这些接口是ABI化的,只要ABI的版本号一致,编译好的原生模块就可以直接使用,而不需要重新编译。ABI化是指将软件接口转化为应用程序二进制接口(ApplicationBinaryInterface)的过程。在编程中,ABI化的目标是确保在不同编译器、操作系统以及硬件平台之间的二进制兼容性。通过将接口规范化为二进制标准,不同模块或程序可以相互调用和交互,而无需关心具体实现细节和底层平台差异。Node-API的设计就是为了实现跨版本和跨平台的ABI兼容性,以便C/C++模块能够在不同的Node.js环境中无需重新编译即可运行。Node-API有哪些开发方式?基于Node-API的原生模块开发可以使用C语言或者基于node-addon-api项目使用C++语言的两种方式。基于C语言开发:由于受众为前端开发者,C语言的编程复杂度高、开发效率较低,开发过程可能较为繁琐。基于node-addon-api项目开发:相对于纯C语言开发,C++提供了更多的高级特性和工具,开发效率相对较高。node-addon-api项目提供了一组方便的C++API封装,简化了与Node-API的交互过程,减少了部分底层操作。接下来我们就基于这个项目来开发一个Electron的原生模块。安装Node.js:首先,确保您已经安装了Node.js,可以从Node.js官网下载并安装适合您操作系统的版本。需要全局安装node-gyp,它是专门为构建开发、编译原生模块环境而生的跨平台命令行工具。npm install -g node-gyp创建空项目目录:创建一个新的项目目录,作为原生模块的开发目录。初始化npm项目:进入项目目录,打开终端,并运行以下命令初始化npm项目。npm init根据提示,设置项目的名称、版本等信息。安装node-addon-api:运行以下命令安装node-addon-api模块:npm install node-addon-api --save-dev以上就已经搭建好了基本的原生模块开发环境,接下来通过一个简单的例子,来实现一个访问系统文件的原生模块带大家了解一下开发流程。开发原生模块访问系统文件并读取文件内容开发原生模块需要熟悉C++编程和Node.js的C/C++扩展开发,涉及一些底层的编程和跨平台的知识。这里的例子较为简单,方便大家理解。创建C++文件:在项目目录下创建一个C++源文件,例如filesystem.cpp,并添加以下代码内容:#include #include #include Napi::String ReadFile(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); // 读取文件路径参数 std::string filePath = info[0].As().Utf8Value(); // 打开文件并读取内容 std::ifstream file(filePath); std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); 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"。创建binding.gyp文件:在项目目录中创建一个名为binding.gyp的文件,并添加以下内容:{ "targets": [ { "target_name": "filesystem", "sources": ["filesystem.cpp"] }}binding.gyp是一个用于配置Node.js原生模块构建过程的项目文件。它采用了JSON格式,并使用特定的语法来定义编译选项、依赖项和源文件等信息。通过编辑binding.gyp文件,可以指定编译器和链接器的选项,添加所需的依赖库,并确定要编译的源文件。编译原生模块在构建编译原生模块时,需要使用node-gyp,它会读取binding.gyp文件并根据其内容进行编译操作。node-gyp提供了一个简化的构建流程,使得开发人员能够轻松地配置和构建原生模块。使用以下命令构建该模块。$ node-gyp configure $ node-gyp build运行上述命令将生成一个名为build/Release/filesystem.node的编译好的原生模块文件。接下来,就可以在任何Node.js文件中使用该模块:创建一个名为app.js的JavaScript文件,并添加以下代码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应用提供更广泛的功能扩展,包括底层系统功能的访问、高性能计算呢?当然有,在日常开发中,可以使用动态链接库(DynamicLinkLibrary,DLL)进行扩展功能的模块化。使用动态链接库可以使用更多的语言和框架进行开发,适合不同开发者的需求。比如,一个go开发者也可以给我们提供一个动态链接库供Electron调用,也可以将go代码打包成不同平台的文件供其他平台调用,更适合独立功能各个平台使用的场景。原生模块则更专注于为Node.js和Electron应用程序提供特定功能的开发。在实际应用中,可根据具体需求和开发团队的技术栈来选择合适的方式,结合动态链接库和原生模块来扩展Electron应用程序的功能。参考链接Electron桌面应用开发:https://juejin.cn/book/7152717638173966349?enter_from=course_center&utm_source=course_centernode-gyp实现nodejs调用C++:https://juejin.cn/post/6844903971220357134?searchId=20231214152519329167E9F0AB744365BF转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~
|
|