SOLFIND
Web Lens
Portal home

原生代码与 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

  1. 跳转到主内容 [direct]
  2. Electron [direct]
  3. 文档 [direct]
  4. 应用开发接口(API) [direct]
  5. 博客 [direct]
  6. Electron Forge [direct]
  7. Electron Fiddle [direct]
  8. 治理 [direct]
  9. 案例展示 [direct]
  10. 资源 [direct]
  11. 版本发布 [direct]
  12. English [direct]
  13. Deutsch [direct]
  14. Español [direct]
  15. Français [direct]
  16. 日本語 [direct]
  17. Português [direct]
  18. Русский [direct]
  19. Electron 中的流程 [direct]
  20. 最佳实践 [direct]
  21. 示例 [direct]
  22. 开发 [direct]
  23. Native Node Modules [direct]
  24. 原生代码与 Electron:C++(Windows) [direct]
  25. Native Code and Electron: Swift (macOS) [direct]
  26. Native Code and Electron: C++ (Linux) [direct]
  27. 分发 [direct]
  28. 检测和调试 [direct]
  29. 引用 [direct]
  30. 参与贡献 [direct]
  31. sqlite [direct]
  32. node-gyp readme [direct]
  33. 受支持的 Python 版本 [direct]
  34. GCC [direct]
  35. napi-rs [direct]
  36. neon [direct]
  37. node-bindgen [direct]
  38. N-API 文档 [direct]
  39. Microsoft C++、C 和汇编程序文档 [direct]
  40. C++/WinRT [direct]
  41. MSVC-170 C++ 文档 [direct]
  42. Apple 开发者文档 [direct]
  43. 使用 Objective-C 编程 [direct]
  44. 编辑此页面 [direct]
  45. 安全 [direct]
  46. Discord [direct]
  47. Bluesky [direct]
  48. X [direct]
  49. Mastodon [direct]
  50. Stack Overflow [direct]
  51. GitHub [direct]
  52. Open Collective [direct]
  53. 基础看板 [direct]
  54. OpenJS Foundation [direct]
  55. 商标政策 [direct]
  56. 商标列表 [direct]
  57. 使用条款 [direct]
  58. 隐私政策 [direct]
  59. 章程 [direct]
  60. 行为准则 [direct]
  61. Cookie 政策 [direct]