从 Electron 中的原生到 JavaScript
Electron 用 C++ 或 Objective-C 编写的功能如何到达 JavaScript,以便最终用户可以使用它们?
背景
Electron 是一个 JavaScript 平台,其主要目的是降低开发人员构建强大的桌面应用程序的门槛,而无需担心特定于平台的实现。然而,在其核心,Electron 本身仍然需要用给定的系统语言编写特定于平台的功能。
实际上,Electron 为您处理原生代码,以便您可以专注于单个 JavaScript API。
但这又是如何工作的呢?Electron 用 C++ 或 Objective-C 编写的功能如何到达 JavaScript,以便最终用户可以使用它们?
要追踪此路径,让我们从 app
模块开始。
通过打开我们 lib/
目录中的 app.ts
文件,您会在顶部找到以下代码行
const binding = process.electronBinding('app');
此行直接指向 Electron 将其 C++/Objective-C 模块绑定到 JavaScript 以供开发人员使用的机制。此函数由 ElectronBindings
类的标头和 实现文件创建。
process.electronBinding
这些文件添加了 process.electronBinding
函数,该函数的行为类似于 Node.js 的 process.binding
。process.binding
是 Node.js 的 require()
方法的较低级别实现,只是它允许用户 require
原生代码而不是用 JS 编写的其他代码。此自定义 process.electronBinding
函数赋予了从 Electron 加载原生代码的能力。
当顶层 JavaScript 模块(如 app
)需要此原生代码时,如何确定和设置该原生代码的状态?方法在哪里暴露给 JavaScript?属性呢?
native_mate
目前,可以在 native_mate
中找到此问题的答案:它是 Chromium 的 gin
库的一个分支,它使在 C++ 和 JavaScript 之间编组类型变得更容易。
在 native_mate/native_mate
中,有一个用于 object_template_builder
的标头和实现文件。这使我们能够在原生代码中形成模块,其形状符合 JavaScript 开发人员的预期。
mate::ObjectTemplateBuilder
如果我们把每个 Electron 模块都看作一个 object
,就更容易理解为什么我们要使用 object_template_builder
来构造它们。这个类构建在 V8 公开的一个类之上,V8 是谷歌用 C++ 编写的开源高性能 JavaScript 和 WebAssembly 引擎。V8 实现了 JavaScript (ECMAScript) 规范,因此其原生功能实现可以直接与 JavaScript 中的实现相关联。例如,v8::ObjectTemplate
为我们提供了没有专用构造函数和原型的 JavaScript 对象。它使用 Object[.prototype]
,在 JavaScript 中相当于 Object.create()
。
要查看此操作,请查看 app 模块的实现文件 atom_api_app.cc
。底部是以下内容
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("getGPUInfo", &App::GetGPUInfo)
在上面的行中,.SetMethod
在 mate::ObjectTemplateBuilder
上调用。.SetMethod
可以在 ObjectTemplateBuilder
类的任何实例上调用,以在 JavaScript 中的 Object 原型上设置方法,语法如下
.SetMethod("method_name", &function_to_bind)
这相当于 JavaScript 的
function App{}
App.prototype.getGPUInfo = function () {
// implementation here
}
此类还包含在模块上设置属性的函数
.SetProperty("property_name", &getter_function_to_bind)
或
.SetProperty("property_name", &getter_function_to_bind, &setter_function_to_bind)
这些将依次是 Object.defineProperty 的 JavaScript 实现
function App {}
Object.defineProperty(App.prototype, 'myProperty', {
get() {
return _myProperty
}
})
和
function App {}
Object.defineProperty(App.prototype, 'myProperty', {
get() {
return _myProperty
}
set(newPropertyValue) {
_myProperty = newPropertyValue
}
})
可以创建具有原型和属性的 JavaScript 对象,正如开发人员所期望的那样,并且可以更清晰地推理在此较低系统级别实现的功能和属性!
关于在何处实现任何给定模块方法的决定本身就是一个复杂且常常不确定的决定,我们将在以后的文章中介绍。