1. 背景
KMP 旨在不同的平台执行 kotlin 编写的代码,本文将探讨它们能够跨平台的原因。
2. 为什么不同的语言可以混合编译?
2.1 C 语言编译过程
首先了解下 C 语言的编译过程。下面是一个 main.c 文件,根据不同的操作系统,打印不同的日志。
在 Linux 系统上,可以使用 GCC 编译器将 main.c 编译为 可执行文件。执行该文件,便会开启一个进程来打印日志。
gcc main.c -o main // 将main.c编译为可执行文件
GCC 的编译过程可以分为 4 个阶段:预处理、编译、汇编、链接。
- 预处理:执行 删除注释、将 include 替换为对应头文件内容、扩展宏 等操作。处理后的文件为 main.i。
- 编译:将 C 语言翻译为 汇编代码。处理后的文件为 main.s。
- 汇编:将汇编文件转换为CPU可以理解的 机器码。处理后的文件为 main.o。
- 链接:由于引用了 stdio.h(C 标准库)中的 printf 函数,所以需要与该库进行链接,才能调用 printf。 假如使用静态链接,会将 printf 函数的机器码复制到可执行文件当中。
通过以上步骤,我们便得到了一个可执行文件。
2.2 C 语言如何与其他语言混编?
可见,编译过程分为多个阶段,每个阶段都可以独立执行。
2.2.1 汇编阶段混编
例如,我们可以自己写部分汇编代码 mylib.s,并将 main.c 先转化为汇编代码。在汇编阶段将 mylib.s、main.s 传给编译器,编译器会将它们混合成可执行文件。
2.2.2 链接阶段混编
不仅能在汇编阶段混合,还能再链接阶段混合。例如,我们 C 文件想调用下面 Rust 写的函数。
利用 Rust 的编译器,将其编译为静态库。然后将 汇编完成的 main.o、rust 静态库 统一交由链接器,最终生成可执行文件,内部包含二者的机器码。
以上就是不同语言可以混合编译的原因,我们可以在编译的任意阶段进行混合。
还有的语言的执行依赖于虚拟机,由虚拟机解释执行代码,其他语言要想与它们混编,需要先生成虚拟机看得懂的代码,后文会有体现。
想了解更多编译相关内容,可参考:www.youtube.com/watch?v=XJC…
3. ios 项目为什么可以使用 kotlin?
KMP 在 ios 上的实现,正是采用上文提到的 “链接阶段混编”。
首先 kotlin/native 编译器将 commonMain 与 iosMain 包中的代码,打包为静态库文件,里面包含的已经是针对 iOS 设备 CPU 架构(如 ARM64)编译好的机器码。
在链接阶段,ios 的链接器将原生机器码与静态库文件进行链接,最终形成 ios app(可执行文件)。
4. Android 项目为什么可以使用 kotlin?
由于 Android 要运行在(不同CPU、不同厂商)的设备上,所以 Android app 不能直接是可执行文件,而是采用 字节码+虚拟机 的方案。具体如下:
- 我们写的 *.Java,会被 Java 编译器编译字节码文件。
- 字节码文件和资源等文件,会被打包成 .apk 文件,可发布和供用户下载。
- Java 虚拟机主要由 C/C++ 实现,所以可以把它看作是一个 C 语言可执行文件,执行这个文件便开启了 Java 虚拟机进程。
- 每打开一个 Android app,相当于启动一个 Java 虚拟机进程,然后把 .apk 文件丢给虚拟机。
- 虚拟机进程执行过程中,会读取字节码中的代码,将其翻译为符合当前 CPU 指令集架构的机器码,然后交由 CPU 执行。
Kotlin 编译器能够将 Kotlin 代码编译成字节码文件,所以 kotlin 和 java 可以相互调用,共同被 Java 虚拟机解释执行。
注:Android 5.0 后,提高运行效率,Android App 在安装时,就会将字节码预编译为本地机器码。