深入浅出Electron:原理、工程与实践
上QQ阅读APP看书,第一时间看更新

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的方法。

其他模块,比如系统剪贴板、屏幕、对话框等,也都是通过这种方式提供底层支持的,这里不再一一赘述。