原生代码与 Electron | Electron
https://www.electronjs.org/zh/docs/latest/tutorial/native-code-and-electron • 112 KB fetched Open original page
原生代码与 Electron | Electron
跳转到主内容
Electron 文档 应用开发接口(API) 博客 工具
* Electron Forge
* Electron Fiddle
社区
* 治理
* 案例展示
* 资源
版本发布 中文
* English
* Deutsch
* Español
* Français
* 日本語
* Português
* Русский
* 中文
搜索
* 开始上手
* Electron 中的流程
* 最佳实践
* 示例
* 开发
* Native Node Modules
* Node 原生模块
* Tutorial: Native Code and Electron
* 原生代码与 Electron
* 原生代码与 Electron:C++(Windows)
* Native Code and Electron: Swift (macOS)
* Native Code and Electron: C++ (Linux)
* 分发
* 检测和调试
* 引用
* 参与贡献
*
* Native Node Modules
* Tutorial: Native Code and Electron
* 原生代码与 Electron 在本页面
原生代码与 Electron
Electron 最强大的功能之一,就是能够将 Web 技术与原生代码相结合 - 能在需要时用于计算密集型逻辑,亦或是少量的原生用户界面。
Electron 是通过在“原生 Node.js 插件”之上构建来实现的。 你很有可能已经遇到了几个了 - 比如著名的 sqlite 就是使用原生代码将 JavaScript 与原生技术结合到了一起。 你可以使用这个特性扩展你的 Electron 应用程序,使其能做到原生应用程序能完成的任何事:
* 访问 JavaScript 里不可用的原生平台 API。 你可以使用 macOS、Windows、Linux 操作系统里的任意 API。
* 创建能与原生桌面框架交互的 UI 组件。
* 集成现有的原生库。
* 实现运行起来比 JavaScript 还快的需要高性能的代码。
原生 Node.js 插件是动态链接的共享对象(在类 Unix 系统上)或者 DLL 文件(在 Windows 上),可以使用 require() 或 import 函数加载到 Node.js 或 Electron 里。 它们的行为就像是普通的 JavaScript 模块,但为用 C++,Rust,或其他可以编译成原生代码的语言编写的代码提供了一个接口。
教程:为 Electron 创建一个原生 Node.js 插件
本教程将带你构建一个能够在 Electron 应用程序内使用的基础 Node.js 原生插件。 我们会聚焦于所有平台都共通的概念,使用 C++ 作为实现语言。 一旦你完成了对所有原生 Node.js 插件都适用的本教程,你就可以移步到我们的平台限定教程的其中一个。
要求
本教程假定你已经安装了 Node.js 和 npm,以及在你的平台上编译代码所需的必要基础工具(比如 Windows 上的 Visual Studio,macOS 上的 Xcode,Linux 上的 GCC/Clang)。 你可以在 node-gyp readme 里找到详细说明。
要求:macOS
要在 macOS 上构建原生 Node.js 插件,你需要安装 Xcode 命令行工具。 其提供了必要的编译器和构建工具(即 clang 、 clang++ 以及 make )。 下列命令会在你尚未安装命令行工具时指引你安装。
xcode-select --install
要求:Windows
官方的 Node.js 安装程序提供了安装“适用于原生模块的工具”的可选项,其会安装编译基础 C++ 模块所需的一切工具 - 具体来说是 Python 3 和 “Visual Studio 使用 C++ 的桌面开发” 工作负荷。 又或者,你可以使用 chocolatey , winget ,或 Windows 商店。
要求:Linux
* 受支持的 Python 版本
* make
* 一个合适的 C/C++ 编译器工具链,比如 GCC 。
1. 创建一个包
首先,创建一个将会包含你的原生插件的 Node.js 包:
mkdir my-native-addon
cd my-native-addon
npm init -y
这会创建一个基础的 package.json 文件。 接下来,我们安装必要的依赖:
npm install node-addon-api bindings
* node-addon-api :这是低级 Node.js API 的 C++ 包装层,使构建插件更加轻松。 它提供了面向对象的 C++ API,比原始的 C 风格 API 更方便和安全。
* bindings :一个能简化编译后的原生插件的加载过程的辅助模块。 它负责自动寻找你编译后的 .node 文件。
现在,我们更新 package.json ,加入适当的构建脚本。 我们会在下文解释这些具体是做什么的。
package.json
{
"name" : "my-native-addon" ,
"version" : "1.0.0" ,
"description" : "A native addon for Electron" ,
"main" : "js/index.js" ,
"scripts" : {
"clean" : "node -e \"require('fs').rmSync('build', { recursive: true, force: true })\"" ,
"build" : "node-gyp configure && node-gyp build"
} ,
"dependencies" : {
"bindings" : "^1.5.0" ,
"node-addon-api" : "^8.3.0"
} ,
"devDependencies" : {
"node-gyp" : "^11.1.0"
}
}
这些脚本将会:
* clean :删除构建目录,以便进行全新构建
* build :运行标准的 node-gyp 构建流程来编译你的插件
2. 搭建构建系统
Node.js 插件使用叫做 node-gyp 的构建系统,这是一个用 Node.js 编写的跨平台命令行工具。 它在后台使用平台特定的构建工具编译 Node.js 的原生插件模块:
* 在 Windows 上:Visual Studio
* 在 macOS 上:Xcode 或命令行工具
* 在 Linux 上:GCC 或类似的编译器
配置 node-gyp
binding.gyp 文件是一个类 JSON 的配置文件,告诉 node-gyp 如何构建你的原生插件。 它类似于 makefile 或工程文件,但采用的是独立于平台的格式。 我们创建一个基础的 binding.gyp 文件:
binding.gyp
{
"targets" : [
{
"target_name" : "my_addon" ,
"sources" : [
"src/my_addon.cc" ,
"src/cpp_code.cc"
] ,
"include_dirs" : [
"<!@(node -p \"require('node-addon-api').include\")" ,
"include"
] ,
"dependencies" : [
"<!(node -p \"require('node-addon-api').gyp\")"
] ,
"defines" : [
"NODE_ADDON_API_CPP_EXCEPTIONS"
] ,
"cflags!" : [ "-fno-exceptions" ] ,
"cflags_cc!" : [ "-fno-exceptions" ] ,
"xcode_settings" : {
"GCC_ENABLE_CPP_EXCEPTIONS" : "YES" ,
"CLANG_CXX_LIBRARY" : "libc++" ,
"MACOSX_DEPLOYMENT_TARGET" : "10.14"
} ,
"msvs_settings" : {
"VCCLCompilerTool" : {
"ExceptionHandling" : 1
}
}
}
]
}
我们分解一下这个配置:
* target_name :你的插件名。 这决定了编译后的模块的文件名(my_addon.node)。
* sources :要编译的源文件列表。 我们有两个文件:主插件文件和实际的 C++ 实现。
* include_dirs :头文件的搜索目录。 看起来晦涩的代码行 <!@(node -p \"require('node-addon-api').include\") 的作用是运行一个 Node.js 命令来获取 node-addon-api 包含目录的路径。
* dependencies : node-addon-api 依赖。 和包含目录类似,这行也执行了一个 Node.js 命令来获取合适的配置。
* defines :预处理器宏定义。 这里我们为 node-addon-api 启用了 C++ 异常。
平台特定设置:
* cflags! 和 cflags_cc! :适用于类 Unix 系统的编译器标志
* xcode_settings :macOS/Xcode 编译器特有的设置
* msvs_settings :Windows 上 Microsoft Visual Studio 特有的设置
现在,为我们的项目创建目录结构:
mkdir src
mkdir include
mkdir js
这将会创建:
* src/ :源文件会放在这里
* include/ :放置头文件
* js/ :放置 JavaScript 包装层
3. 来自 C++ 的“Hello World”
先从在头文件里定义我们的 C++ 接口起步。 创建 include/cpp_code.h :
# pragma once
# include <string>
namespace cpp_code {
// 一个简单的函数,接受一个字符串输入并返回一个字符串
std :: string hello_world ( const std :: string & input ) ;
} // cpp_code 命名空间
#pragma once 指令是一个头文件防护指令,能够避免文件在相同的编译单元内被多次包含。 实际的函数声明放在一个命名空间里面,以避免潜在的命名冲突。
接下来,我们在 src/cpp_code.cc 里实现这个函数:
src/cpp_code.cc
# include <string>
# include "../include/cpp_code.h"
namespace cpp_code {
std :: string hello_world ( const std :: string & input ) {
// 简单拼接字符串然后返回
return "Hello from C++! You said: " + input ;
}
} // cpp_code 命名空间
这是个简单的实现,只是向输入字符串添加一些文本然后返回它。
现在,让我们编写将 C++ 代码与 Node.js/JavaScript 世界连接起来的插件代码。 创建 src/my_addon.cc :
src/my_addon.cc
# include <napi.h>
# include <string>
# include "../include/cpp_code.h"
// 创建一个将会暴露给 JavaScript 的类
class MyAddon : public Napi :: ObjectWrap < MyAddon > {
public :
// 这个静态方法定义了 JavaScript 的类
static Napi :: Object Init ( Napi :: Env env , Napi :: Object exports ) {
// 定义带有方法的 JavaScript 类
Napi :: Function func = DefineClass ( env , "MyAddon" , {
InstanceMethod ( "helloWorld" , & MyAddon :: HelloWorld )
} ) ;
// 创建构造函数的持久引用
Napi :: FunctionReference * constructor = new Napi :: FunctionReference ( ) ;
* constructor = Napi :: Persistent ( func ) ;
env . SetInstanceData ( constructor ) ;
// 在 exports 对象上设置构造函数
exports . Set ( "MyAddon" , func ) ;
return exports ;
}
// 构造函数
MyAddon ( const Napi :: CallbackInfo & info )
: Napi :: ObjectWrap < MyAddon > ( info ) { }
private :
// 将会暴露给 JavaScript 的方法
Napi :: Value HelloWorld ( const Napi :: CallbackInfo & info ) {
Napi :: Env env = info . Env ( ) ;
// 验证参数(只需要一个字符串)
if ( info . Length ( ) < 1 || ! info [ 0 ] . IsString ( ) ) {
Napi :: TypeError :: New ( env , "Expected string argument" ) . ThrowAsJavaScriptException ( ) ;
return env . Null ( ) ;
}
// 将 JavaScript 字符串转换为 C++ 字符串
std :: string input = info [ 0 ] . As < Napi :: String > ( ) ;
// 调用我们的 C++ 函数
std :: string result = cpp_code :: hello_world ( input ) ;
// 将 C++ 字符串转换回 JavaScript 字符串并返回
return Napi :: String :: New ( env , result ) ;
}
} ;
// 初始化插件
Napi :: Object Init ( Napi :: Env env , Napi :: Object exports ) {
return MyAddon :: Init ( env , exports ) ;
}
// 注册初始化函数
NODE_API_MODULE ( my_addon , Init )
我们分解一下这个代码:
* 我们定义了一个继承自 Napi::ObjectWrap<MyAddon> 的 MyAddon 类,它负责为 JavaScript 包装我们的 C++ 类。
* Init 静态方法:2.1 定义了一个带有叫做 helloWorld 的方法的 JavaScript 类2.2 为构造函数创建了一个持久引用(为了避免受到垃圾回收影响)2.3 导出类的构造函数
* 该构造函数只是将其参数传递给了父类。
* HelloWorld 方法:
4.1 获取 Napi 环境4.2 验证参数(只需要一个字符串)4.3 将 JavaScript 字符串转换为 C++ 字符串4.4 调用我们的 C++ 函数4.5 将 C++ 字符串转换回 JavaScript 字符串并返回它
* 我们定义了一个初始化函数并使用 NODE_API_MODULE 宏注册它,这会让我们的模块能够被 Node.js 加载.
现在,我们创建一个 JavaScript 包装层,让插件更容易使用。 创建 js/index.js :
js/index.js
const EventEmitter = require ( 'node:events' )
// 使用“bindings”模块加载原生插件
// 它会在各种地方寻找编译后的 .node 文件
const bindings = require ( 'bindings' )
const native = bindings ( 'my_addon' )
// 创建一个不错的 JavaScript 包装层
class MyNativeAddon extends EventEmitter {
constructor ( ) {
super ( )
// 创建我们 C++ 类的一个实例
this . addon = new native . MyAddon ( )
}
// 用更友好的 JavaScript API 包装 C++ 方法
helloWorld ( input = '' ) {
if ( typeof input !== 'string' ) {
throw new TypeError ( 'Input must be a string' )
}
return this . addon . helloWorld ( input )
}
}
// 导出单个实例
if ( process . platform === 'win32' || process . platform === 'darwin' || process . platform === 'linux' ) {
module . exports = new MyNativeAddon ( )
} else {
// 为不支持的平台提供后备方案
console . warn ( 'Native addon not supported on this platform' )
module . exports = {
helloWorld : ( input ) => ` Hello from JS! You said: ${ input } `
}
}
这个 JavaScript 包装层:
* 使用 bindings 加载我们已编译的原生插件
* 创建一个继承自 EventEmitter 的类(适用于未来可能需要触发事件的扩展功能)
* 实例化我们的 C++ 类并提供一个更简单的 API
* 在 JavaScript 侧添加一些输入验证
* 导出我们包装层的单个实例
* 优雅地处理平台不受支持的情况
构建并测试插件
现在我们可以构建我们的原生插件:
npm run build
这会运行 node-gyp configure 和 node-gyp build 将我们的 C++ 代码编译为一个 .node 文件。
让我们创建一个简单的测试脚本来验证一切是否正常工作。 在项目根目录创建 test.js :
test.js
// 加载我们的插件
const myAddon = require ( './js' )
// 尝试调用 helloWorld 函数
const result = myAddon . helloWorld ( 'This is a test' )
// 应当会输出:“Hello from C++! You said: This is a test”
console . log ( result )
运行测试:
node test.js
如果一切正常,你应该会看到:
Hello from C++! You said: This is a test
在 Electron 里使用插件
要在一个 Electron 应用程序里使用这个插件,你需要:
* 将其作为依赖项包含在你的 Electron 项目中
* 针对你特定的 Electron 版本构建它。 electron-forge 自动为你处理这一步 - 要获取更多详情,请参阅 Node 原生模块 。
* 在一个启用了 Node.js 的进程里像其他模块一样导入并使用它。
// 在你的主进程内
const myAddon = require ( 'my-native-addon' )
console . log ( myAddon . helloWorld ( 'Electron' ) )
参考资料和延伸阅读
原生插件开发可以使用 C++ 以外的几种语言编写。 Rust 可以使用像 napi-rs , neon ,或 node-bindgen 之类的 Crate 来开发。 在 macOS 上可以通过 Objective-C++ 使用 Objective-C/Swift。
具体的实现细节因平台差异而有很大的不同,尤其是访问平台特定的 API 或者 UI 框架的时候,比如 Windows 的 Win32 API、COM 组件、UWP/WinRT - 或者 macOS 的 Cocoa、AppKit、ObjectiveC 运行时。
这就意味着你编写原生代码时很可能需要两组参考资料:首先,在 Node.js 侧,使用 N-API 文档 来学习如何创建并向 JavaScript 暴露复杂的结构体 - 比如异步线程安全函数的调用或者创建 JavaScript 原生对象( error 、 promise 等)。 其次,在你所使用的技术侧,你很可能需要查阅它们的底层文档:
* Microsoft C++、C 和汇编程序文档
* C++/WinRT
* MSVC-170 C++ 文档
* Apple 开发者文档
* 使用 Objective-C 编程
编辑此页面
上一个
Node 原生模块
下一个
原生代码与 Electron:C++(Windows)
* 要求
* 要求:macOS
* 要求:Windows
* 要求:Linux
* 1. 创建一个包
* 2. 搭建构建系统
* 配置 node-gyp
* 3. 来自 C++ 的“Hello World”
* 构建并测试插件
* 在 Electron 里使用插件
* 参考资料和延伸阅读
文档
* 入门指南
* API 参考
清单
* 性能
* 安全
工具
* Electron Forge
* Electron Fiddle
社区
* 治理
* 资源
* Discord
* Bluesky
* X
* Mastodon
* Stack Overflow
更多
* GitHub
* Open Collective
* 基础看板
版权所有 OpenJS Foundation 及 Electron 贡献者。保留所有权利。 OpenJS Foundation 已注册商标并使用商标。如需查看 OpenJS Foundation 的商标列表,请参阅我们的 商标政策 和 商标列表 。未在 OpenJS Foundation 商标列表 中标明的商标和标志,均为其各自持有者的商标™或注册商标®。使用它们并不意味着与它们有任何关联或得到它们的认可。 OpenJS Foundation | 使用条款 | 隐私政策 | 章程 | 行为准则 | 商标政策 | 商标列表 | Cookie 政策
Hosting and infrastructure graciously provided by
Links found on this page
- 跳转到主内容 [direct]
- Electron [direct]
- 文档 [direct]
- 应用开发接口(API) [direct]
- 博客 [direct]
- Electron Forge [direct]
- Electron Fiddle [direct]
- 治理 [direct]
- 案例展示 [direct]
- 资源 [direct]
- 版本发布 [direct]
- English [direct]
- Deutsch [direct]
- Español [direct]
- Français [direct]
- 日本語 [direct]
- Português [direct]
- Русский [direct]
- Electron 中的流程 [direct]
- 最佳实践 [direct]
- 示例 [direct]
- 开发 [direct]
- Native Node Modules [direct]
- 原生代码与 Electron:C++(Windows) [direct]
- Native Code and Electron: Swift (macOS) [direct]
- Native Code and Electron: C++ (Linux) [direct]
- 分发 [direct]
- 检测和调试 [direct]
- 引用 [direct]
- 参与贡献 [direct]
- sqlite [direct]
- node-gyp readme [direct]
- 受支持的 Python 版本 [direct]
- GCC [direct]
- napi-rs [direct]
- neon [direct]
- node-bindgen [direct]
- N-API 文档 [direct]
- Microsoft C++、C 和汇编程序文档 [direct]
- C++/WinRT [direct]
- MSVC-170 C++ 文档 [direct]
- Apple 开发者文档 [direct]
- 使用 Objective-C 编程 [direct]
- 编辑此页面 [direct]
- 安全 [direct]
- Discord [direct]
- Bluesky [direct]
- X [direct]
- Mastodon [direct]
- Stack Overflow [direct]
- GitHub [direct]
- Open Collective [direct]
- 基础看板 [direct]
- OpenJS Foundation [direct]
- 商标政策 [direct]
- 商标列表 [direct]
- 使用条款 [direct]
- 隐私政策 [direct]
- 章程 [direct]
- 行为准则 [direct]
- Cookie 政策 [direct]
|
|