跳至主要内容

从原生代码到 Electron 中的 JavaScript

·阅读时长 4 分钟

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.bindingprocess.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 是 Google 的开源高性能 JavaScript 和 WebAssembly 引擎,用 C++ 编写。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)

在上面的代码行中,在 mate::ObjectTemplateBuilder 上调用了 .SetMethod.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)

它们反过来将是以下的 JavaScript 实现 Object.defineProperty

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 对象,并且可以更清晰地推断出在该底层系统级实现的函数和属性!

关于在何处实现任何给定模块方法的决策本身是一个复杂且往往非确定性的问题,我们将在以后的文章中进行介绍。