Kotlin Native实战开发
16.1 Kotlin Native
16.1.1 Kotlin Native简介
Kotlin Native是一种将Kotlin源码编译成不需要任何虚拟机支持的目标平台二进制数据的技术。编译后的二进制数据可以直接运行在目标平台上,它主要包含一个基于LLVM的后端编译器和一个Kotlin本地运行时库。设计Kotlin Native的目的是为了支持在非JVM环境下进行编程,例如在嵌入式平台和iOS环境下,从而让Kotlin可以运行在非JVM平台。
LLVM(Low Level Virtual Machine)是一种底层虚拟机技术,由C++编写,主要用于优化应用程序的编译时间、链接时间、运行时间以及空闲时间。LLVM能有效解决编译器重复编译代码的问题,并制定了LLVM IR中间代码表示语言,充分考虑各种应用场景,提高代码编译效率。
在深入讲解Kotlin Native之前,先回顾一下计算机高级语言的两种常见流派:编译型语言和解释型语言。编译型语言使用专门的编译器,针对特定平台或操作系统将高级语言源代码一次性编译成该平台硬件能够执行的机器码。编译生成的可执行程序可以脱离开发环境,在特定平台上独立运行。由于是一次性编译成机器码,编译型语言通常运行效率较高,但生成的可执行程序无法移植到其他平台运行,例如C和C++。解释型语言则使用专门的解释器对源程序进行逐行解释,并生成特定平台的机器码并立即执行。解释型语言通常不需要整体编译和链接处理,运行效率较低且不能脱离解释器独立运行,但便于实现源程序的移植和运行。
16.1.2 Kotlin Native编译器
目前,Kotlin Native主要提供Mac、Linux和Windows三个主流平台的编译器,可以轻松编译出运行在树莓派、iOS、OS X、Windows以及Linux系统上的程序。Kotlin Native支持的平台和版本包括:Windows x86_64、Linux x86_64、arm32、MIPS、MIPS小端、MacOS x86_64、iOS arm64、Android arm32、arm64以及WebAssembly wasm32。
编译Kotlin Native项目时,首先需要从GitHub下载Kotlin Native的编译器软件包。下载对应平台版本后解压,其目录结构包括bin、klib等目录。此外,也可以通过克隆Kotlin Native编译器的源码进行编译。下载源码后,使用命令 ./gradlew dependencies:update 下载依赖关系,然后建立编译器和库的关联。构建整个项目可能需要较长时间,使用命令 ./gradlew dist distPlatformLibs 即可编译项目。
编译完成后,可以得到Kotlin的Native编译器,通常位于项目的 ./dist/bin 目录下。Native编译器由七个可执行程序构成,包括cinterop、jsinterop、klib、konanc、kotlinc、kotlinc-native和run_konan。通过对比可以发现,Native编译器的目录结构与Kotlin Native官方提供的编译器内容相同。然后,可以利用Native编译器编译应用程序。例如,设置环境变量后使用 kotlinc hello.kt -o hello 编译。如果需要优化编译,可以使用 -opt 参数。对于应用程序测试,可以使用类似 ./gradlew backend.native:tests:run 的命令。
在Kotlin Native官方示例中,系统自带了针对不同平台的例子,这些例子可以直接编译运行。由于Kotlin Native本身是一个Gradle构建的项目,可以使用IntelliJ IDEA直接打开Kotlin Native目录下的samples文件,IDEA会自动识别该项目。
16.1.3 编译器konan
打开kotlin-native-macos-0.6文件,其目录结构包括bin目录、klib目录和konan目录。bin目录包含与Kotlin Native相关的执行命令,klib目录包含Kotlin标准库的关联元数据文件以及针对各个目标平台的bc文件,konan目录包含编译器依赖的一些JAR包和一些已编译好的项目实例,可以直接使用IntelliJ IDEA导入。
打开Kotlin Native编译器的bin目录,可以发现主要由cinterop、jsinterop、klib、konanc、kotlinc、kotlinc-native、run_konan等七个可执行文件组成。其中,run_konan是编译器的真正入口,其源码显示Kotlin Native编译器的运行环境仍然需要JVM支持,但它生成的机器码可执行程序不需要JVM环境,可以直接运行在对应的平台系统上。
16.2 Kotlin Native实例
16.2.1 构建Kotlin Native项目
首先,在IDEA中依次选择【File】→【New】→【Project】创建一个普通的Gradle工程。
16.2.2 添加konan插件配置
创建完成后,需要修改build.gradle文件配置。添加以下内容:
buildscript {
repositories {
mavenCentral()
maven {
url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.5"
}
}
apply plugin: 'konan'
其中,kotlin-native-gradle-plugin:0.5是Gradle构建Kotlin Native工程所使用的DSL插件,发布在https://dl.bintray.com/jetbrains/kotlin-native-dependencies仓库。还需要应用konan插件,用于将Kotlin代码编译为原生代码。更多信息可参考https://github.com/JetBrains/kotlin-native/blob/master/GRADLE_PLUGIN.md。此时,需要创建一个kotliner.def文件,用于配置C源码到Kotlin的映射关系。
16.2.3 编写源代码
在工程的src目录下新建一个c目录,用于存放C代码。创建两个C文件:cn_kotliner.h和cn_kotliner.c。C头文件cn_kotliner.h的声明代码如下:
#ifndef CN_KOTLINER_H
#define CN_KOTLINER_H
void printHello();
int factorial(int n);
#endif
cn_kotliner.c的源代码如下:
#include "cn_kotliner.h"
#include <stdio.h>
void printHello() {
printf("[C]HelloWorld\n");
}
int factorial(int n) {
printf("[C]calc factorial: %d\n", n);
if (n == 0) return 1;
return n * factorial(n - 1);
}
接下来,创建一个Kotlin文件,用于调用C层代码,实现跨平台调用。源码如下:
import kotliner.*
fun main(args: Array<String>) {
printHello()
(1..5).map(::factorial).forEach(::println)
}
其中,导入的kotliner.*包是C语言代码经过clang编译后对应的C接口包路径,可以在项目的build.gradle配置文件中的konanInterop中配置。
16.2.4 添加konanInterop与konanArtifacts配置
添加konanInterop配置,用于配置Kotlin调用C的接口:
konanInterop {
ckotlinor {
defFile 'kotliner.def'
includeDirs "src/c"
}
}
ckotlinor是插件中的KonanInteropConfig对象,在konanArtifacts配置中会引用。kotliner.def是Kotlin Native与C语言互操作的配置文件,内容如下:
headers=cn_kotlinor.h
compilerOpts=-Isrc/c
konanInterop还提供其他常用选项,如defFile、pkg、target、compilerOpts、linkerOpts、headers、includeDirs、linkFiles和dumpParameters,具体含义可参考相关文档。
接下来,添加konanArtifacts配置,用于处理编译任务:
konanArtifacts {
KotlinorClient {
inputFiles fileTree("src/kotlin")
useInterop 'ckotlinor'
nativeLibrary fileTree('src/c/cn_kotlinor.bc')
target 'macbook'
}
}
konan编译任务配置的处理类是KonanCompileTask.kt,位于Kotlin Native的kotlin-native-gradle-plugin插件中。
16.2.5 编译与执行
在项目的src/c目录下,使用命令行编译C代码:
clang -std=c99 -c cn_kotliner.c -o cn_kotliner.bc -emit-llvm
clang是基于LLVM的C/C++/Objective-C/Objective-C++编译器。如果找不到clang命令,可以在编译器的dependencies目录中查找。也可以使用shell脚本来简化编译命令。
编译后得到cn_kotliner.bc库文件。在执行Gradle构建前,需要指定konan编译器主目录。在工程根目录创建gradle.properties文件,例如:
konan.home=/Users/xiangzhihong/kotlin native/kotlin-native-macos-0.5
如果不添加此文件,编译时需使用本地编译器。
然后在IDEA的Gradle工具栏中执行构建操作。构建完成后,在项目的build/konan/bin目录下生成KotlinorClient.kexe可执行程序,可以直接在Mac OS系统上运行,无需JVM环境。在命令行中执行KotlinorApp.kexe命令,即可看到输出结果。
16.2.6 命令行方式编译Kotlin Native
除了Gradle方式,还可以使用命令行方式编译Kotlin Native项目。编写完Kotlin源码后,采用Makefile脚本方式构建。例如,创建一个Makefile:
build : src/kotlin/main.kt kotliner.kt.bc
konanc src/kotlin/main.kt -library build/kotliner/kotliner.kt.bc -nativelibrary build/kotliner/cn_kotliner.bc -o build/kotliner/kotliner.kexe
kotliner.kt.bc : kotliner.bc kotliner.def
cinterop -def ./kotliner.def -o build/kotliner/kotliner.kt.bc
kotliner.bc : src/c/cn_kotliner.c src/c/cn_kotliner.h
mkdir -p build/kotliner
clang -std=c99 -c src/c/cn_kotliner.c -o build/kotliner/cn_kotliner.bc -emit-llvm
clean:
rm -rf build/kotliner
编译时,需要将编译器<konan.home>/bin目录加入系统PATH环境变量,然后执行make命令。编译完成后,在build/kotliner目录中找到kotliner.kexe文件。
Kotlin Native致力于跨平台开发,注重语言平台的互操作性,使用Kotlin Native进行跨平台开发具有明显优势。