2.11 支持不同的操作系统
有很多种方案可以使代码在不同的操作系统中提供相同的接口或能力,比如基于Electron开发软件的工程师常用的就是在软件的运行期判断当前的操作系统,然后执行不同的业务单元,代码如下所示:
const homedir = require("os").homedir(); let result; if (process.platform === "win32") { result = process.env.LOCALAPPDATA || path7.join(homedir, "AppData", "Local"); } else if (process.platform === "darwin") { result = path.join(homedir, "Library", "Application Support", "Caches"); } else { result = process.env.XDG_CACHE_HOME || path7.join(homedir, ".cache"); } return result;
在这段代码中使用process.platform判断当前用户的操作系统,为不同的操作系统生成不同的用户数据目录。这种方法的缺点就是当代码执行到这个逻辑时,要消耗额外的CPU来执行判断比较。
对于基于Qt开发软件的工程师来说,最常用的就是使用Qt框架内预定义的宏变量来判断当前的操作系统。
#ifdef Q_OS_WIN32 qputenv("MY_ENV", "WIN"); #elif Q_OS_MAC qputenv("MY_ENV", "MAC"); #else qputenv("MY_ENV", "LINUX"); #endif
在上述代码的编译过程中就会根据Q_OS_WIN32和Q_OS_MAC宏变量的值来编译不同的代码,这种方案的优点就是代码执行过程中不必消耗额外的CPU资源来执行判断比较,因为编译器已经把这个判断工作做了。
如果使用Visual Studio作为开发工具,还可以在工程里定义预处理器变量,如图2-5所示。
图2-5 在Visual Studio中添加预处理器变量
这通常用于针对不同的编译环境提供不同的逻辑,比如debug环境、test环境和各种灰度环境等。
但Electron的工程师并没有使用上述这些方案来为不同的操作系统提供不同的逻辑,我们以AddRecentDocument方法为例来分析Electron是如何支持不同的操作系统的。
AddRecentDocument方法可以为应用添加最近打开的文档,在VS Code的任务栏图标上右击,就可以看到最近打开的文件夹和最近打开的文件,如图2-6所示。
图2-6 VS Code最近打开的文档
这就是VS Code使用AddRecentDocument这个API开发的功能,很显然这个API的实现逻辑在不同的操作系统下是不一样的。
通过翻阅源码可知,AddRecentDocument方法是在shell\browser\browser.h头文件中定义的,在shell\browser\browser_win.cc和shell\browser\browser_mac.mm文件内都有实现逻辑,其中browser_win.cc内的代码是Windows操作系统下的实现逻辑,browser_mac.mm是Mac操作系统下的实现逻辑。
虽然shell\browser\browser_linux.cc文件内也实现了这个方法,但这个方法并没有任何代码,是一个空方法,这就是AddRecentDocument文档说明此方法仅支持macOS和Windows操作系统的原因,在browser_linux.cc放置一个空方法是为了避免同一套代码在Linux系统中执行时产生异常(即使什么也不做,也不应该产生异常)。
虽然为不同的操作系统实现了不同的代码,但这些代码是如何生效的呢?这就要看filenames.gni文件中定义的编译文件的路径数组,代码如下所示:
lib_sources_win = [ "shell/browser/browser_win.cc", ...... // 此处省略N个文件路径 ] lib_sources_mac = [ "shell/browser/browser_mac.mm", ...... // 此处省略N个文件路径 ] lib_sources_mac = [ "shell/browser/browser_mac.mm", ...... // 此处省略N个文件路径 ] lib_sources_linux = [ "shell/browser/browser_linux.cc", ...... // 此处省略N个文件路径 ] lib_sources = [ "shell/browser/browser.h", ...... // 此处省略N个文件路径 ]
前三个数组分别定义了在不同操作系统下执行编译工作时需要编译的文件,最后一个数组定义了三个系统都需要编译的文件。
接下来编译脚本BUILD.gn使用了这三个数组,代码如下所示:
sources = filenames.lib_sources if (is_win) { sources += filenames.lib_sources_win } if (is_mac) { sources += filenames.lib_sources_mac } if (is_linux) { sources += filenames.lib_sources_linux }
也就是说,除了一些所有系统都需要的源码文件外,不同系统有各自不同的源码文件,这样在不同平台下编译出的二进制文件则拥有了相同的接口但不同的实现。
如大家所见,Electron也是在编译期判断执行环境的,所以不存在性能上的损耗。