Electron 插件开发实践
前言
早期跨平台桌面应用开发大多采用 Qt 和 C++,受语言学习成本开发效率影响,越来越多的人将目光转向了 Electron。Electron 是以 Nodejs 和 Chromium 为内核的跨平台开发框架。
Electron 基于 Web 技术开发桌面应用,Web 技术在软件开发领域应用非常广泛,生态较为成熟,学习成本较低、开发效率高。但是 Web 在处理多线程、并发场景时显得捉襟见肘,Electron 底层有 Nodejs 支持,Nodejs 的插件模块具有调用 C++ 的能力,C++ 非常适合处理高并发、音视频等复杂业务,弥补了 Web 的性能问题。本文就 js 和 C++ 混合编程在 Electron 桌面程序中的应用进行介绍。
Nodejs 中使用 C++,有以下几种方式:
将 C++ 程序作为独立子进程使用。
通过 node-ffi 方式调用。
Nodejs 扩展,将 C++ 代码编译为 Nodejs 模块,本文主要针对这种方式进行介绍。
C++ 扩展
C++ 扩展简介
Nodejs 本身采用 C++ 编写,所以我们可以使用 C++ 编写的自己的 Nodejs 模块,可以像 Nodejs 原生模块一样使用。C++ 扩展格式为 .node,其本质为动态链接库,相当于 Windows 下 .dll。C++ 扩展作为动态链接库,通过 dlopen 在 Nodejs 中加载。
C++ 扩展架构图:
C++ 扩展实现的几种方式
实现 C++ 扩展有 3 种方式:原生模式、nan、Node-API。
原生模式
直接使用 Nodejs API 及 Chrome V8 API 进行开发,这种方式早已被遗弃。
特点:Nodejs API 和 Chrome V8 API 接口一旦变化,依赖这些 API 的 C++ 扩展便无法使用,特定版本的 C++ 扩展只能在对应版本 Nodejs 环境中使用。
nan(Native Abstractions for Nodejs)
nan 是 Nodejs 抽象接口集,nan 根据当前 Nodejs 版本,使用宏判断执行对应版本的 API。
特点:C++ 扩展在不同版本 Nodejs 中运行,需重新编译,Nodejs 升级到较高版本后出现接口不兼容问题。
Node-API
Node-API 使用 Nodejs 二进制接口,相比 nan 方式这些二进制接口更为稳定。
特点:不同版本 Nodejs 只要 abi 版本号一致,C++ 扩展可以直接使用无需重新编译,消除了 Nodejs 版本差异。
构建工具
node-gyp
node-gyp 对 gyp(Chromium 编写的构建工具)进行了封装,binding.gyp 为其配置文件。
node-gyp 工作分为两个过程:
a. 结合 binding.gyp 生成对应平台下的工程配置,比如:Windwos 下生成 .sln 项目文件。
b. 项目文件编译,生成 C++ 扩展。
binding.gyp 配置文件,以 Windows 为例:
字段说明:
target_name:目标的名称,此名称将用作生成的 Visual Studio 解决方案中的项目名称。
type:可选项:static_library 静态库、executable 可执行文件、shared_library 共享库。
defines:将在编译命令行中传入的 C 预处理器定义(使用 -D 或 /D 选项)。
include_dirs:C++ 头文件所在的目录。
sources:C++ 源文件。
conditions:适配不同环境配置条件块。
copies:拷贝 dll 动态库到生成目录。
library_dirs: 配置 lib 库目录到 vs 项目中。
libraries:项目依赖的库。
msvs_settings:Visual Studio 中属性设置。
node-gyp 编译指令:
cmake-js
cmake-js 与 node-gyp 工作原理类似。cmake-js 是基于 CMake 的构建系统,而 node-gyp 是基于 Goole 的 gyp 工具,这里不在进行详细介绍。
回调事件处理
Nodejs 运行在单线程中,但它能够支持高并发,就是依赖事件循环实现。简单来说 Nodejs 主线程维护一个事件队列,收到一个耗时任务将任务放入队列,继续向下执行其他任务。主线程空闲时,遍历事件队列,非 I/O 任务亲自处理,通过回调函数返回给上层调用。I/O 任务放入线程池执行,并指定回调函数,然后继续执行其他任务。
C++ 扩展调用 js 回调函数时,会在 Nodejs 挂在一个 libuv 线程池,用于处理回调函数,当 Nodejs 主线程空闲时,去遍历线程池,处理任务。libuv 具体细节参考 nertc-electron-sdk:
混合编程实践
示例 1
结合 node-addon-api 进行演示,node-addon-api 对 Node-API 接口进行了封装开发简单。该实例完成 js 调用 C++ 函数实现两个数字相加。
项目结构
package.json 配置文件
binding.gyp 配置文件
C++ 扩展
js 调用 C++ 扩展
在 package.json 目录下,执行 npm install、npm run test,可以看到 js 调用 C++ 接口成功,输出两个数字相加结果。
示例 2
网易云信音视频通话 nertc-electron-sdk,采 Node-API 方式进行开发,将 C++ 原生 sdk 封装成 Nodejs 模块(nertc-electron-sdk.node),结合 Electron 可以快速实现音视频通话。github demo 体验地址:https://github.com/netease-im/Basic-Video-Call/tree/master/Group-Video/NERtcSample-GroupVideoCall-Electron
常见问题
Electron 应用中 js 调用 C++ 扩展时,提示 Error: The specified module could not be found。
答:该错误表示能找到 C++ 扩展模块(.node)但是加载失败,因为 .node 会依赖其他 .dll 和 C++ 运行库,缺少这些库时就会报上面的错误,使用 depends 查看缺少哪种库,配置即可。
运行使用 C++ 扩展的 Electron 应用,提示 The specifield module could not be found。
答:该错误表示找不到 C++ 扩展模块。在项目 package.json 文件中配置 extraFiles 字段,将扩展拷贝到 Electron 可加载目录即可。
Electron 加载 C++ 扩展时提示:Module parse failed: Unexpected character '�'。
答:webpack 只能识别 js 和 json 文件无法识别 C++ 扩展模式,在 Electron 打包时需要在 vue.config.js 中配置 C++ 扩展的 loader。
更多常见问题汇总:
https://doc.yunxin.163.com/docs/jcyOTA0ODM/jU4NTEwNzg?platformId=50456#9
版权声明: 本文为 InfoQ 作者【网易云信】的原创文章。
原文链接:【http://xie.infoq.cn/article/5ad2aa2ea40f7458271304d8c】。文章转载请联系作者。
评论