16.2 快速开始 Hello World

16.2.1 运行环境准备

我们直接去 Github上面去下载 kotlin-native 编译器的软件包。下载地址是 :https://github.com/JetBrains/kotlin-native/releases

Kotlin极简教程

下载解压之后,我们可以看到 Kotlin Native 编译器 konan 的目录如下:

  1. -rw-r--r--@ 1 jack staff 6828 6 20 22:47 GRADLE_PLUGIN.md
  2. -rw-r--r--@ 1 jack staff 16286 6 20 22:47 INTEROP.md
  3. -rw-r--r--@ 1 jack staff 1957 6 21 01:03 README.md
  4. -rw-r--r--@ 1 jack staff 4606 6 20 22:47 RELEASE_NOTES.md
  5. drwxr-xr-x@ 8 jack staff 272 6 20 23:04 bin
  6. drwxr-xr-x 6 jack staff 204 7 28 17:08 dependencies
  7. drwxr-xr-x@ 3 jack staff 102 6 20 23:01 klib
  8. drwxr-xr-x@ 5 jack staff 170 5 12 00:02 konan
  9. drwxr-xr-x@ 4 jack staff 136 5 12 00:02 lib
  10. drwxr-xr-x@ 22 jack staff 748 6 22 19:04 samples

关于这个目录里面的内容我们在后面小节中介绍。

另外,我们也可以自己下载源码编译,这里就不多说了。

16.2.2新建 Gradle 工程

在本小节中,我们先来使用IDEA 来创建一个普通的 Gradle 工程。

第1步,打开 File -> New -> Project ,如下图所示

Kotlin极简教程

第2步,新建Gradle项目。我们直接在左侧栏中选择 Gradle,点击 Next

Kotlin极简教程

第3步,设置项目的 GroupId、ArtifactId、Version 信息

Kotlin极简教程

第4步,配置 Gradle 项目的基本设置。我们直接选择本地的 Gradle 环境目录,省去下载的时间(有时候网络不好,要下载半天),具体配置如下图所示

Kotlin极简教程

第5步,配置项目名称和项目存放目录,点击 Finish

Kotlin极简教程

第6步,等待 IDEA 创建完毕,我们将得到一个如下的Gradle 工程

Kotlin极简教程

现在这个工程里面什么都没有。下面我们就来开始原始的手工新建文件编码。

16.2.3 源代码目录

首先我们在工程根目录下面新建 src 目录,用来存放源代码。在 src 下面新建 c 目录存放 C 代码,新建 kotlin 目录存放 Kotlin 代码。我们的源代码组织结构设计如下

  1. src
  2. ├── c
  3. ├── cn_kotlinor.c
  4. ├── cn_kotlinor.h
  5. └── kotlin
  6. └── main.kt

16.2.4 C 代码文件

cn_kotlinor.h

C头文件中声明如下

```c#ifndef CN_KOTLINOR_H#define CN_KOTLINOR_H
void printHello();
int factorial(int n);
int fib(int n);#endif

  1. 我们简单声明了3个函数。
  2. #### cn_kotlinor.c
  3. C 源代码文件内容如下
  4. ```c#include "cn_kotlinor.h"#include <stdio.h>
  5. void printHello(){
  6. printf("[C]HelloWorld\n");
  7. }
  8. int factorial(int n){
  9. printf("[C]calc factorial: %d\n", n);
  10. if(n == 0) return 1;
  11. return n * factorial(n - 1);
  12. }
  13. int fib(int n){
  14. printf("[C]calc fibonacci: %d\n", n);
  15. if(n==1||n==2) return 1;
  16. return fib(n-1) + fib(n-2);
  17. }

这就是我们熟悉的 C 语言代码。

16.2.5 Kotlin 代码文件

main.kt 文件内容如下

  1. import ckotlinor.*
  2. fun main(args: Array<String>) {
  3. printHello()
  4. (1..7).map(::factorial).forEach(::println)
  5. (1..7).map(::fib).forEach(::println)
  6. }

其中,import kotlinor.* 是 C 语言代码经过 clang 编译之后的C 的接口包路径,我们将在下面的 build.gradle 配置文件中的konanInterop中配置这个路径。

16.2.6 konan插件配置

首先,我们在 build.gradle 里面添加构建脚本 buildscript 闭包

  1. buildscript {
  2. repositories {
  3. mavenCentral()
  4. maven {
  5. url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
  6. }
  7. }
  8. dependencies {
  9. classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.3"
  10. }
  11. }

这里我们添加了Gradle 构建 Kotlin Native 工程的 DSL 插件 kotlin-native-gradle-plugin:0.3 。这里的版本号,对应我们下载的 konan 编译器的版本号,我们使用的是 v0.3,所以这里我们也使用0.3版本的插件。这个插件发布在https://dl.bintray.com/jetbrains/kotlin-native-dependencies仓库里,所以我们在repositories里面添加了这个仓库。

然后,我们应用插件 konan

  1. apply plugin: 'konan'

konan 就是用来编译 Kotlin 为 native 代码的插件。

16.2.7 konanInterop 互操作配置

  1. konanInterop {
  2. ckotlinor {
  3. defFile 'kotlinor.def' // interop 的配置文件
  4. includeDirs "src/c" // C 头文件目录,可以传入多个
  5. }
  6. }

konanInterop 主要用来配置 Kotlin 调用 C 的接口。konanInterop 的配置是由konan 插件API中的 KonanInteropTask.kt来处理的(这个类的源码地址)。

这里我们声明的 ckotlinor 是插件中的KonanInteropConfig 对象。我们在下面的konanArtifacts里面会引用这个 ckotlinor 。

关于konanInterop的配置选项有

  1. konanInterop {
  2. pkgName {
  3. defFile <def-file>
  4. pkg <package with stubs>
  5. target <target: linux/macbook/iphone/iphone_sim>
  6. compilerOpts <Options for native stubs compilation>
  7. linkerOpts <Options for native stubs >
  8. headers <headers to process>
  9. includeDirs <directories where headers are located>
  10. linkFiles <files which will be linked with native stubs>
  11. dumpParameters <Option to print parameters of task before execution>
  12. }
  13. // TODO: add configuration for konan compiler
  14. }

我们简要说明如下表所示

配置项 功能说明
defFile 互操作映射关系配置文件
pkg C 头文件编译后映射为 Kotlin 的包名
target 编译目标平台:linux/macbook/iphone/iphone_sim等
compilerOpts 编译选项
linkerOpts 链接选项
headers 要处理的头文件
includeDirs 包括的头文件目录
linkFiles 与native stubs 链接的文件
dumpParameters 打印 Gradle 任务参数选项配置

其中,kotlinor.def 是Kotlin Native 与 C 语言互操作的配置文件,我们在kotlinor.def 里面配置 C 源码到 kotlin 的映射关系。这个文件内容如下

kotlinor.def

  1. headers=cn_kotlinor.h
  2. compilerOpts=-Isrc/c

同样的配置,如果我们写在 build.gradle 文件中的konanInterop配置里如下

  1. konanInterop {
  2. ckotlinor {
  3. // defFile 'kotlinor.def' // interop 的配置文件
  4. compilerOpts '-Isrc/c'
  5. headers 'src/c/cn_kotlinor.h' // interop 的配置文件
  6. includeDirs "src/c" // C 头文件存放目录,可以传入多个
  7. }
  8. }

关于这个配置文件的解析原理可以参考 KonanPlugin.kt 文件的源码

16.2.8 konanArtifacts 配置

在 konan 插件中,我们使用konanArtifacts来配置编译任务执行。

  1. konanArtifacts {
  2. KotlinorApp { // (1)
  3. inputFiles fileTree("src/kotlin") // (2)
  4. useInterop 'ckotlinor' // (3)
  5. nativeLibrary fileTree('src/c/cn_kotlinor.bc') // (4)
  6. target 'macbook' // (5)
  7. }
  8. }

其中,(1)处的KotlinorApp名称,在 build 之后会生成以这个名称命名的 KotlinorApp.kexe 可执行程序。
(2)处的inputFiles配置的是 kotlin 代码目录,程序执行的入口 main 定义在这里。

(3)处的useInterop 配置的是使用哪个互操作配置。我们使用的是前面的 konanInterop 里面的配置 ckotlinor 。

(4) 处的nativeLibrary配置的是本地库文件。关于’src/c/cn_kotlinor.bc’文件的编译生成我们在下面讲。

(5) 处的target 配置的是编译的目标平台,这里我们配置为 ‘macbook’ 。

关于konanArtifacts可选的配置如下所示

  1. konanArtifacts {
  2. artifactName1 {
  3. inputFiles "files" "to" "be" "compiled"
  4. outputDir "path/to/output/dir"
  5. library "path/to/library"
  6. library File("Library")
  7. nativeLibrary "path/to/library"
  8. nativeLibrary File("Library")
  9. noStdLib
  10. produce "library"|"program"|"bitcode"
  11. enableOptimization
  12. linkerOpts "linker" "args"
  13. target "target"
  14. languageVersion "version"
  15. apiVersion "version"
  16. }
  17. artifactName2 {
  18. extends artifactName1
  19. inputDir "someDir"
  20. outputDir "someDir"
  21. }
  22. }

konan 编译任务配置处理类是KonanCompileTask.kt

16.2.9 完整的 build.gradle 配置

完整的 build.gradle 配置文件内容如下

  1. group 'com.easy.kotlin'
  2. version '1.0-SNAPSHOT'
  3. buildscript {
  4. repositories {
  5. mavenCentral()
  6. maven {
  7. url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
  8. }
  9. }
  10. dependencies {
  11. classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.3"
  12. }
  13. }
  14. apply plugin: 'konan' // konan 就是用来编译 Kotlin 为 native 代码的插件
  15. konanInterop { // konanInterop 主要用来配置 Kotlin 调用 C 的接口
  16. ckotlinor {
  17. defFile 'kotlinor.def' // interop 的配置文件
  18. includeDirs "src/c" // C 头文件目录,可以传入多个
  19. }
  20. }
  21. konanArtifacts { //konanArtifacts 配置我们的项目
  22. KotlinorApp { // build 之后会生成 KotlinorApp.kexe 可执行程序
  23. inputFiles fileTree("src/kotlin") //kotlin 代码配置,项目入口 main 需要定义在这里
  24. useInterop 'ckotlinor' //使用前面的 konanInterop 里面的配置 kotlinor{ ... }
  25. nativeLibrary fileTree('src/c/cn_kotlinor.bc') //自己编译的 llvm 字节格式的依赖
  26. target 'macbook' // 编译的目标平台
  27. }
  28. }

提示:关于konan 插件详细配置文档:Gradle DSL

16.2.10 使用 clang 编译 C 代码

为了实用性,我们新建一个 shell 脚本 kclang.sh 来简化 clang 编译的命令行输入参数
```#!/usr/bin/env bash
clang -std=c99 -c $1 -o $2 -emit-llvm

  1. 这样,我们把 kclang.sh 放到 C 代码目录下,然后直接使用脚本来编译:

kclang.sh cn_kotlinor.c cn_kotlinor.bc

  1. 我们将得到一个 cn_kotlinor.bc 库文件。
  2. 提示:clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/Objective-C++编译器。它与GNU C语言规范几乎完全兼容。更多关于 clang 的内容可参考 http://clang.llvm.org/docs/index.html 。
  3. ### 16.2.11 配置 konan 编译器主目录
  4. 最后,在执行 Gradle 构建之前,我们还需要指定konan 编译器主目录。我们在工程根目录下面新建 gradle.properties 这个属性配置文件,内容如下

konan.home=/Users/jack/soft/kotlin-native-macos-0.3

  1. ### 16.2.12 执行构建操作
  2. 我们直接在 IDEA 右侧的 Gradle 工具栏点击Tasks ->build -> build 命令执行构建操作
  3. ![Kotlin极简教程](/projects/EasyKotlin/16.使用 Kotlin Native/images/kotlin_native_08.png)
  4. 我们会看到终端输出

15:12:02: Executing external task ‘build’…
:assemble UP-TO-DATE
:check UP-TO-DATE
:downloadKonanCompiler
:genKotlinerInteropStubs
:compileKotlinerInteropStubs
KtFile: kotliner.kt
:compileKonanKotliner
KtFile: main.kt
ld: warning: object file (/var/folders/q5/kvt7_nsd6ngdw5qry4d99xv00000gn/T/combined697750051437954502.o) was built for newer OSX version (10.12) than being linked (10.11)
:compileKonan
:build

BUILD SUCCESSFUL in 29s
4 actionable tasks: 4 executed
15:12:31: External task execution finished ‘build’.

  1. 构建完成之后,会在build/konan/bin/目录下面生成一个KotlinorApp.kexe可执行程序,它直接在 Mac OS 上运行,不再依赖JVM 环境了。我们得到的完整的构建输出目录树如下

build
└── konan
├── bin
│ ├── KotlinorApp.kexe
│ └── KotlinorApp.kt.bc
├── interopCompiledStubs
│ └── ckotlinorInteropStubs
│ ├── ckotlinorInteropStubs
│ │ ├── linkdata
│ │ │ ├── module
│ │ │ ├── package_ckotlinor
│ │ │ └── root_package
│ │ ├── manifest
│ │ ├── resources
│ │ └── targets
│ │ └── macbook
│ │ ├── kotlin
│ │ │ └── program.kt.bc
│ │ └── native
│ └── ckotlinorInteropStubs.klib
├── interopStubs
│ └── genCkotlinorInteropStubs
│ └── ckotlinor
│ └── ckotlinor.kt
└── nativelibs
└── genCkotlinorInteropStubs
└── ckotlinorstubs.bc

16 directories, 10 files

  1. 其中在 ckotlinor.kt中,我们可以看出 konan 编译器还为我们生成了 C 代码对应的 Kotlin 的接口
  2. ```kotlin
  3. @file:Suppress("UNUSED_EXPRESSION", "UNUSED_VARIABLE")
  4. package ckotlinor
  5. import konan.SymbolName
  6. import kotlinx.cinterop.*
  7. fun printHello(): Unit {
  8. val res = kni_printHello()
  9. return res
  10. }
  11. @SymbolName("ckotlinor_kni_printHello")
  12. private external fun kni_printHello(): Unit
  13. fun factorial(n: Int): Int {
  14. val _n = n
  15. val res = kni_factorial(_n)
  16. return res
  17. }
  18. @SymbolName("ckotlinor_kni_factorial")
  19. private external fun kni_factorial(n: Int): Int
  20. fun fib(n: Int): Int {
  21. val _n = n
  22. val res = kni_fib(_n)
  23. return res
  24. }
  25. @SymbolName("ckotlinor_kni_fib")
  26. private external fun kni_fib(n: Int): Int

我们在Kotlin 代码中,调用的就是这些映射到 C 中的函数接口。

16.2.12 执行 kexe 应用程序

我们直接在命令行中执行 KotlinorApp.kexe 如下

  1. chatper16_kotlin_native_helloworld$ build/konan/bin/KotlinorApp.kexe

我们可以看到如下输出:

  1. [C]HelloWorld
  2. [C]calc factorial: 1
  3. [C]calc factorial: 0
  4. [C]calc factorial: 2
  5. ...
  6. [C]calc factorial: 2
  7. [C]calc factorial: 1
  8. [C]calc factorial: 0
  9. 1
  10. 2
  11. 6
  12. 24
  13. 120
  14. 720
  15. 5040
  16. [C]calc fibonacci: 1
  17. [C]calc fibonacci: 2
  18. [C]calc fibonacci: 3
  19. ...
  20. [C]calc fibonacci: 3
  21. [C]calc fibonacci: 2
  22. [C]calc fibonacci: 1
  23. 1
  24. 1
  25. 2
  26. 3
  27. 5
  28. 8
  29. 13

至此,我们完成了一次简单的Kotlin Native 与 C 语言互操作在系统级编程的体验之旅。

我们看到,Kotlin Native仍然看重互操作性(Interoperability)。它能高效地调用C函数,甚至还能从C头文件自动生成了对应的Kotlin接口,发扬了JetBrains为开发者服务的良好传统!

但是,在体验的过程中我们也发现整个过程比较手工化,显得比较繁琐(例如手工新建各种配置文件、手工使用 clang 编译C 代码等)。

不过,Kotlin Native 的 Gradle 插件用起来还是相当不错的。相信未来 IDEA 会对 Kotlin Native 开发进行智能的集成,以方便系统编程的开发者更好更快的完成项目的配置以及开发编码工作。