跳到主要内容

从原生代码到 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)

在上面这行代码中,.SetMethodmate::ObjectTemplateBuilder 上被调用。可以在 ObjectTemplateBuilder 类的任何实例上调用 .SetMethod,以使用以下语法在 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 对象,并且能够更清晰地理解在这个较低系统级别实现的函数和属性!

关于在何处实现任何给定模块方法的决定本身就是一个复杂且常常不确定的问题,我们将在未来的文章中探讨。