注:本部分内容来源于《Kotlin入门与实战》,预计9月上市。
16.1 Kotlin Native
16.1.1 Kotlin Native简介
Kotlin Native是一种将Kotlin源码编译成不需要任何VM支持的目标平台二进制数据的技术,编译后的二进制数据可以直接运行在目标平台上,它主要包含一个基于LLVM的后端编译器的和一个Kotlin本地运行时库。设计Kotlin Native的目的是为了支持在非JVM环境下进行编程,如在嵌入式平台和iOS环境下,如此一来,Kotlin就可以运行在非JVM平台环境下。
LLVM是Low Level Virtual Machine的缩写,是一种比较底层的虚拟机技术,LLVM由C 编写而成,主要用来优化应用程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time)。LLVM可以有效的解决编译器重复编译代码的问题,并且LLVM制定了LLVM IR这种中间代码表示语言,LLVM IR充分考虑了各种应用场景,有效的提高了代码的编译效率。
在讲解Kotlin Native具体的知识之前,先来看一下计算机高级语言常见两种流派:编译型语言和解释型语言。
所谓编译型语言,是指使用专门的编译器、针对特定平台/操作系统将某种高级语言源代码一次性编译成该平台硬件能够执行的机器码,编译生成的可执行程序可以脱离开发环境,在特定的平台上独立运行。因为编译型语言是一次性编译成机器码的,所以可以脱离开发环境独立运行,而且通常运行效率较高。不过,正因为编译型语言只能被编译成特定平台上的机器码,所以生成的可执行程序通常无法移植到其他平台上运行。例如,现在比较流行的C、C 等高级编程语言都属于编译型语言。
而所谓解释型语言,是指使用专门的解释器对源程序进行逐行解释,并生成特定平台的机器码并立即执行的编程语言。解释型语言通常不需要进行整体的编译和链接处理,解释型语言会把编译型语言中的编译和解释过程混合在一起执行。虽然解释型语言运行效率较低且不能脱离释器独立运行,但解释型语言可以很方便的实现源程序的移植和运行。
##16.1.2 Kotlin Native编译器 目前,Kotlin Native主要提供了Mac、Linux和Windows三个主流平台的编译器,使用该编译器可以很轻松的编译出运行在树莓派、iOS、OS X、Windows以及Linux系统上的程序。Kotlin Native支持平台和版本如下表所示。
代码语言:javascript复制支持的系统平台 支持的版本
Windows x86_64
Linux x86_64、arm32、MIPS、MIPS小端
MacOS x86_64
iOS arm64
Android arm32、arm64
WebAssembly wasm32
表16-1 Kotlin Native支持平台及其版本
编译Kotlin Native项目,首先需要到Github上下载Kotlin Native的编译器软件包,下载地址为:https://github.com/JetBrains/kotlin-native/releases,使用前下载对应的平台版本即可,下载后解压下载的Kotlin Native编译器软件包,其目录结构如图16-1所示。
图16-1 Kotlin Native编译器目录结构图 当然,也可以通过克隆Kotlin Native编译器的源码进行编译,编译需要先到Github上下载编译器源码,下载地址为:https://github.com/JetBrains/kotlin-native。下载完成后,使用如下命令下载依赖关系,命令如下:
代码语言:javascript复制./gradlew dependencies:update
然后,建立编译器和库的关联。
代码语言:javascript复制./gradlew bundle
如果需要构建整个项目可能需要很长的时间。然后,使用以下的命令即可编译项目。
代码语言:javascript复制./gradlew dist distPlatformLibs
到此,就可以得到Kotlin的Native编译器了,它通常位于项目的./dist/bin目录下,打开bin文件可以看到Native编译器的相关信息,它有7个可执行程序构成,如图15-2所示。通过对比发现,Native编译器的目录结构和Kotlin Native官方提供的编译器的内容是一样的。然后,就可以利用Native编译器编译应用程序了。例如:
代码语言:javascript复制export PATH=./dist/bin:$PATH
kotlinc hello.kt -o hello
如果需要进行优化编译,可以使用-opt参数。
代码语言:javascript复制kotlinc hello.kt -o hello -opt
如果需要对应用程序进行测试,可以使用类似于下面的命令。
代码语言:javascript复制./gradlew backend.native:tests:run
图16-2 Kotlin的Native编译器目录结构 在Kotlin Native官方提供的示例中,系统自带了针对不同平台的例子,并且这些例子都是可以直接编译运行的。由于Kotlin Native本身是一个gradle构建的项目,所以可以使用idea直接打开Kotlin Native目录下的samples文件,idea会自动识别该项目。 ##16.1.3 编译器konan 打开kotlin-native-macos-0.6文件,其目录结构如图15-3所示。其中,bin目录包含众多的与Kotlin Native相关的执行命令,klib目录则主要包含Kotlin的标准库的关联元数据文件以及针对各个目标平台的bc文件,konan主要包含编译器依赖的一些jar包和一些已经编译好的项目实例,可以使用IntelliJ IDEA直接导入。
图16-3 编译器konan目录结构 打开Kotlin Native编译器的bin目录可以发现,bin文件主要由cinterop、jsinterop、klib、konanc、kotlinc、kotlinc-native、run_konan等7个可执行文件组成。其中,run_konan是编译器真正的入口,源码如下。
代码语言:javascript复制TOOL_NAME="$1"
shift
if [ -z "$JAVACMD" -a -n "$JAVA_HOME" -a -x "$JAVA_HOME/bin/java" ]; then
JAVACMD="$JAVA_HOME/bin/java"
else
JAVACMD=java
fi
[ -n "$JAVACMD" ] || JAVACMD=java
//省略部分代码
LIBCLANG_DISABLE_CRASH_RECOVERY=1
$TIMECMD "$JAVACMD" "${java_opts[@]}" "${java_args[@]}" -cp "$KONAN_CLASSPATH" "$TOOL_CLASS" "$TOOL_NAME" "${konan_args[@]}"
可以发现,Kotlin Native编译器konan的运行环境还是需要JVM环境支持的,但是它生成的机器码的可执行程序是不需要JVM环境支持的,可以直接运行在对应的平台系统上。
16.2 Kotlin Native实例
##16.2.1 构建Kotlin Native项目 首先,在IDEA中依次选择【File】→【New】→【Project】创建一个普通的 Gradle工程。
图16-4 创建Gradle工程 ##16.2.2 添加konan插件配置 创建完成之后,需要修改build.gradle文件配置。打开build.gradle文件并添加如下配置。
代码语言:javascript复制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插件,konan插件是用来将Kotlin代码编译为native代码的插件,可以通过如下地址来获取更多konan相关的信息。
代码语言:javascript复制https://github.com/JetBrains/kotlin-native/blob/master/GRADLE_PLUGIN.md
此时,还需要创建一个kotliner.def文件,该文件主要用来配置C源码到Kotlin的映射关系。
代码语言:javascript复制headers=cn_kotliner.h
##16.2.3 编写源代码 接下来,在工程的src目录下新建一个c目录,此目录专门用来存放C代码。首先,创建两个c文件:cn_kotliner.h和cn_kotliner.c。其中,C头文件声明的代码如下。
代码语言:javascript复制#ifndef CN_KOTLINER_H
#define CN_KOTLINER_H
void printHello();
int factorial(int n);
#endif
在上面的代码中,主要声明了两个函数,打印HelloWorld的 printHello函数和用来计算阶乘的factorial函数。cn_kotlinor.c的源代码如下:
代码语言:javascript复制#include "cn_kotliner.h"
#include <stdio.h>
void printHello(){
printf("[C]HelloWorldn");
}
int factorial(int n){
printf("[C]calc factorial: %dn", n);
if(n == 0) return 1;
return n * factorial(n - 1);
}
接下来,还需要创建一个kotlin文件,该文件主要是调用C层的代码,实现跨平台调用。该文件的源码如下:
代码语言:javascript复制import kotliner.*
fun main(args: Array<String>) {
printHello()
(1..5).map(::factorial).forEach(::println)
}
其中,导入的kotlinor.*包是C语言代码经过clang编译后对应的C接口的包路径,可以在项目的build.gradle配置文件中的konanInterop中配置这个路径。
图16-5 Kotlin Native项目目录结构图
##16.2.4 添加konanInterop与konanArtifacts配置 接下来,还需要添加konanInterop和konanArtifacts相关的配置信息。其中,konanInterop主要用来配置Kotlin调用C的接口。相关的源码如下:
代码语言:javascript复制konanInterop {
ckotlinor {
defFile 'kotlinor.def' // interop配置文件
includeDirs "src/c" // C头文件目录,可以传入多个
}
}
在上面的配置文件中,ckotlinor是插件中的KonanInteropConfig对象,在konanArtifacts配置中会引用这个ckotlinor。而kotlinor.def是Kotlin Native与C 语言互操作的配置文件,可以在kotlinor.def里面配置C源码到Kotlin的映射关系,该配置文件的内容如下。
代码语言:javascript复制headers=cn_kotlinor.h
compilerOpts=-Isrc/c
除此上面使用的选项之外,konanInterop还提供了如下常用的选项。
代码语言:javascript复制konanInterop {
pkgName {
defFile <def-file>
pkg <package with stubs>
target <target: linux/macbook/iphone/iphone_sim>
compilerOpts <Options for native stubs compilation>
linkerOpts <Options for native stubs >
headers <headers to process>
includeDirs <directories where headers are located>
linkFiles <files which will be linked with native stubs>
dumpParameters <Option to print parameters of task before execution>
}
}
konanInterop配置参数选项对应的具体含义如下表所示。
代码语言:javascript复制配置选项 选项说明
defFile 互操作映射关系配置文件
pkg C头文件编译后映射为Kotlin的包名
target 编译目标平台:linux/macbook/iphone等
compilerOpts 编译选项
linkerOpts 链接选项
headers 需要处理的头文件
includeDirs 包括头文件的目录
linkFiles 与native stubs链接的文件
dumpParameters 打印Gradle任务参数的选项配置
表16-2 konanInterop配置选项说明表 接下来,需要为项目添加konanArtifacts相关的配置,该配置主要用来处理编译任务的执行。
代码语言:javascript复制konanArtifacts {
KotlinorClient {
inputFiles fileTree("src/kotlin") //kotlin代码配置,项目入口main()
useInterop 'ckotlinor' //前面的interop配置
nativeLibrary fileTree('src/c/cn_kotlinor.bc') //本地库文件配置
target 'macbook' // 编译的目标平台
}
}
konan编译任务配置的处理类是KonanCompileTask.kt,可以在Kotlin Native的kotlin-native-gradle-plugin插件中找到该类。可以通过以下地址来获取更详细的konan插件配置信息。
代码语言:javascript复制https://github.com/JetBrains/kotlin-native/blob/master/GRADLE_PLUGIN.md
16.2.5 编译与执行
接下来,在项目的src/c目录下面,用命令行编译上面的代码,命令如下。
代码语言:javascript复制clang -std=c99 -c cn_kotliner.c -o cn_kotliner.bc -emit-llvm
其中,clang是一个由C 编写的基于LLVM的C/C /Objective-C/Objective-C 编译器。如果提示找不到clang命令,可以在编译器的dependencies目录中找到相关的内容。当然,还可以使用shell脚本(名称为kclang.sh)来简化clang编译的命令行输入参数。
代码语言:javascript复制#!/usr/bin/env bash clang -std=c99 -c $1 -o $2 -emit-llvm
接着把kclang.sh放到C代码目录下,然后使用脚本来编译C代码。例如:
代码语言:javascript复制kclang.sh cn_kotlinor.c cn_kotlinor.bc
通过上面的命令编译之后,将得到一个名为cn_kotlinor.bc的库文件。最后,在执行Gradle构建之前,还需要指定konan编译器主目录。具体的,在工程根目录下面新建一个gradle.properties属性配置文件,该文件格式如下。
代码语言:javascript复制konan.home=<编译器路径>
例如:
代码语言:javascript复制konan.home=/Users/xiangzhihong /kotlin native/kotlin-native-macos-0.5
当然,也可以不添加gradle.properties配置文件,那样的话,只需要在编译的时候使用本地的编译器即可。 然后,在IDEA的Gradle工具栏依次点击【Tasks】→【build】执行构建操作,如图15-5所示。等待项目构建完成,会在项目的build/konan/bin/目录下面生成一个KotlinorClient.kexe的可执行程序,它可以直接运行在Mac OS系统上而不再需要依赖JVM环境。
图16-5 使用Gradle工具栏编译项目 然后,在命令行中执行KotlinorApp.kexe命令,即可看到输出结果,对应的命令如下。 build/konan/bin/KotlinorApp.kexe 可以看到,作为一款致力于跨平台开发的编程语言,Kotlin Native非常注重语言平台的互操作性,可以说,使用Kotlin Native进行跨平台开发优势是非常明显的。
16.2.6 命令行方式编译Kotlin Native
对于Kotlin Native项目来说,除了允许Gradle方式构建编译外,还可以使用命令行的方式来编译项目。具体来说,编写完Kotlin源码之后,采用shell脚本的方式来构建,或者使用Makefile或build.sh的方式来构建,官方推荐使用shell脚本构建方式,本篇采用与之类似的Makefile脚本方式。例如:
代码语言:javascript复制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
采用命令行方式编译Kotlin Native时,需要先把编译器<konan.home>/bin
目录加入系统的path环境中,然后再执行make命令,编译完成之后就可以在项目的build/kotliner目录中找到kotliner.kexe文件。