- 深入浅出Electron:原理、工程与实践
- 刘晓伦
- 1063字
- 2021-12-14 15:15:53
2.8 提供系统底层支持
开发者可能会有疑问,Electron提供了很多能力,比如访问系统剪贴板、系统快捷键、系统屏幕及电源等硬件设备,难道这些能力都是上面这一系列TypeScript文件做到的?
答案如你所料,无论TypeScript还是JavaScript都做不了这些工作,这些工作都是C++代码实现的,也就是前面所说的shell目录下的C++文件。接下来就分析一下这些C++代码是如何起作用的。
前面讲到在Electron初始化Node.js环境期间,会执行NodeBindings对象的Initialize方法,这个方法内部会调用一个名为RegisterBuiltinModules的方法(shell\common\node_bindings.cc),并注册了Electron为Node.js提供的扩展库,其执行的逻辑非常简单,代码如下所示:
void NodeBindings::RegisterBuiltinModules() { #define V(modname) _register_##modname(); ELECTRON_BUILTIN_MODULES(V) #if BUILDFLAG(ENABLE_VIEWS_API) ELECTRON_VIEWS_MODULES(V) #endif #if BUILDFLAG(ENABLE_DESKTOP_CAPTURER) ELECTRON_DESKTOP_CAPTURER_MODULE(V) #endif #undef V }
其中ELECTRON_BUILTIN_MODULES是一个宏,在编译过程中这个宏会被替换成如下这些代码:
#define ELECTRON_BUILTIN_MODULES(V) \ V(electron_browser_app) \ V(electron_browser_auto_updater) \ V(electron_browser_browser_view) \ V(electron_browser_content_tracing) \ V(electron_browser_crash_reporter) \ V(electron_browser_dialog) \ V(electron_browser_event) \ V(electron_browser_event_emitter) \ V(electron_browser_global_shortcut) \ V(electron_browser_in_app_purchase) \ V(electron_browser_menu) \ V(electron_browser_message_port) \ V(electron_browser_net) \ V(electron_browser_power_monitor) \ V(electron_browser_power_save_blocker) \ V(electron_browser_protocol) \ V(electron_browser_printing) \ V(electron_browser_session) \ V(electron_browser_system_preferences) \ V(electron_browser_base_window) \ V(electron_browser_tray) \ V(electron_browser_view) \ V(electron_browser_web_contents) \ V(electron_browser_web_contents_view) \ V(electron_browser_web_frame_main) \ V(electron_browser_web_view_manager) \ V(electron_browser_window) \ V(electron_common_asar) \ V(electron_common_clipboard) \ V(electron_common_command_line) \ V(electron_common_environment) \ V(electron_common_features) \ V(electron_common_native_image) \ V(electron_common_native_theme) \ V(electron_common_notification) \ V(electron_common_screen) \ V(electron_common_shell) \ V(electron_common_v8_util) \ V(electron_renderer_context_bridge) \ V(electron_renderer_crash_reporter) \ V(electron_renderer_ipc) \ V(electron_renderer_web_frame)
这一系列的操作就是在注册Electron的扩展模块的底层支持模块,实际上V也是一个宏,这个宏对应的代码在具体的C++模块内部,以app模块为例(shell\browser\api\electron_api_app.cc),代码如下(注意下面代码中有一个Initialize方法指针):
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_app, Initialize)
这又是一个宏,定义它的代码如下所示:
#define NODE_LINKED_MODULE_CONTEXT_AWARE(modname, regfunc) \ NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_LINKED)
NODE_MODULE_CONTEXT_AWARE_CPP还是一个宏,它是由Node.js提供的,这个宏最终负责为Node.js注册原生模块,也就是执行上述Initialize方法(实际上Node.js内部的原生模块,比如fs模块,也是由这个宏注册的),Initialize方法的代码如下所示:
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused, v8::Local<v8::Context> context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); gin_helper::Dictionary dict(isolate, exports); dict.Set("app", electron::api::App::Create(isolate)); }
这段代码为一个字典容器添加了一项数据,这项数据的键是app,值是一个由electron::api::App::Create创建的异步对象,类似ipcMain等对象也被存放在这个字典容器中。
当上层应用首次使用app对象时,会执行这个异步对象的初始化方法,以后使用这个对象都会从缓存中获取,构造app对象的部分代码如下所示:
gin_helper::EventEmitterMixin<App>::GetObjectTemplateBuilder(isolate) .SetMethod("quit", base::BindRepeating(&Browser::Quit, browser)) .SetMethod("exit", base::BindRepeating(&Browser::Exit, browser)) .SetMethod("focus", base::BindRepeating(&Browser::Focus, browser)) .SetMethod("setAppPath", &App::SetAppPath) .SetMethod("getAppPath", &App::GetAppPath) // 省略了很多代码
到这里Electron才真正地为app对象提供了一系列的能力,我们再回头看对应的TypeScript文件的代码(lib\browser\api\app.ts),有如下几行代码:
const bindings = process._linkedBinding('electron_browser_app'); const { app } = bindings; export default app;
其中process._linkedBinding是Node.js提供的方法,其内部调用了一个名为getLinked-Binding的方法。这个方法负责获取C++编译的底层模块,同时也就触发了app对象的异步构造工作,得到这个对象之后,Electron就把它暴露给最终开发者使用了。
当开发者通过let {app} = require("electron")的方式得到这个app对象后,就可以使用它的quit或getAppPath方法了,而这些方法都是通过C++代码实现的。
当然,并不是所有app对象的属性和方法都是C++代码实现的,TypeScript代码也为其附加了一些方法或属性,示例代码如下所示:
Object.defineProperty(app, 'applicationMenu', { get () { return Menu.getApplicationMenu(); }, set (menu: Electron.Menu | null) { return Menu.setApplicationMenu(menu); } });
上述TypeScript代码为app对象附加了一个名为applicationMenu的方法。
其他模块,比如系统剪贴板、屏幕、对话框等,也都是通过这种方式提供底层支持的,这里不再一一赘述。