C++或Objective-C写的Electron功能如何被JavaScript访问,以便最终用户可以使用?Electron是一个JavaScript平台,旨在让开发者轻松构建跨平台桌面应用,但核心功能仍以C++或Objective-C等系统语言编写。实际上,Electron处理了原生代码细节,使开发者能专注于统一的JavaScript API。那么,这些原生功能是如何暴露给JavaScript的呢?
背景
Electron通过绑定机制将原生代码功能连接到JavaScript。例如,在Electron的app模块中,可以看到以下代码:
const binding = process.electronBinding('app');
这行代码直接指向Electron的绑定机制,用于将C++或Objective-C模块绑定到JavaScript供开发者使用。该函数由ElectronBindings类的头文件和实现文件创建。
process.electronBinding
process.electronBinding函数类似于Node.js的process.binding,后者是Node.jsrequire()方法的底层实现,允许加载原生代码而非JS代码。这个自定义的process.electronBinding函数赋予了从Electron加载原生代码的能力。当顶级JavaScript模块(如app)需要原生代码时,如何确定和设置原生代码的状态?方法和属性又在哪里暴露给JavaScript?
native_mate
答案在于native_mate,它是Chromiumgin库的一个分支,简化了C++和JavaScript之间的类型交互。在native_mate目录中,object_template_builder的头文件和实现文件允许我们构建符合JavaScript开发者期望的模块形状。
mate::ObjectTemplateBuilder
如果将每个Electron模块视为一个对象,就更容易理解为何使用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)
在这里,.SetMethod被调用在mate::ObjectTemplateBuilder上。.SetMethod可以在任何ObjectTemplateBuilder类实例上调用,以在JavaScript的Object原型上设置方法,语法如下:
.SetMethod("method_name", &function_to_bind)
这相当于JavaScript中的:
function App{}
App.prototype.getGPUInfo = function () {
// 实现代码
}
此外,这个类还包含设置模块属性的函数:
.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对象,并更清晰地推理底层系统级别实现的函数和属性。关于在何处实现特定模块方法的决策本身是复杂且往往非确定性的,这将在未来的文章中探讨。