• 上一篇文章我讲了用 @CName 这个神奇的注解,可以配置 Kotlin Native 函数在符号表中的名字,进而根据 Jni 静态绑定的规则来对应到 Java nat...
        

    上一篇文章 我讲了用 @CName 这个神奇的注解,可以配置 Kotlin Native 函数在符号表中的名字,进而根据 Jni 静态绑定的规则来对应到 Java native 方法,但实际开发当中我们更喜欢用动态注册的方式,因为一方面不受名字的约束,不影响代码重构,函数名也相对美观,另一方面调用起来也相对高效,节省了静态绑定的查找过程。

    如果大家习惯用 C 写动态绑定的代码,那么 Kotlin Native 写起来思路也是很简单的,只要依样画葫芦,就可以写出来,我们先给出代码:

    1. @CName("JNI_OnLoad")

    2. fun JNI_OnLoad(vm: CPointer<JavaVMVar>, preserved: COpaquePointer): jint {

    3.    return memScoped {

    4.        val envStorage = alloc<CPointerVar<JNIEnvVar>>()

    5.        val vmValue = vm.pointed.pointed!!

    6.        val result = vmValue.GetEnv!!(vm, envStorage.ptr.reinterpret(), JNI_VERSION_1_6)

    7.        __android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "JNI_OnLoad")

    8.        if(result == JNI_OK){

    9.            val env = envStorage.pointed!!.pointed!!

    10.            val jclass = env.FindClass!!(envStorage.value, "com/example/hellojni/HelloJni".cstr.ptr)


    11.            val jniMethod = allocArray<JNINativeMethod>(1)

    12.            jniMethod[0].fnPtr = staticCFunction(::sayHello2)

    13.            jniMethod[0].name = "sayHello2".cstr.ptr

    14.            jniMethod[0].signature = "()V".cstr.ptr

    15.            env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 1)


    16.            __android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "register say hello2, %d, %d", sizeOf<CPointerVar<JNINativeMethod>>(), sizeOf<JNINativeMethod>())

    17.        }

    18.        JNI_VERSION_1_6

    19.    }

    20. }

    思路很简单,就是先通过 CName 注解搞定 JNI_OnLoad 函数,让 Java 虚拟机能够在加载 so 库的时候找到这个入口函数,那么我们接下来就是纯调用 Jni 的 C 接口了。

    再说下 memScope 这个东西,C 当中内存管理是人工不智能的,Kotlin Native 则有自己的内存管理机制,因此如果我们需要在 Kotlin Native 当中访问 C 接口,并且创建 C 变量,就需要通过 memScope 来提醒 Kotlin Native 这些变量需要来统一管理。

    获取 JNIEnv 的指针时我们首先构造了一个指针的左值类型:

    1. val envStorage = alloc<CPointerVar<JNIEnvVar>>()

    这么说有些奇怪,总之在 C 的指针类型向 Kotlin Native 映射时, CPointer 的左值类型会映射成 CPointerVar,我现在对 Kotlin Native 与 C 的交互还没有仔细研究,就暂时不展开说了,等后面有机会再系统介绍 Kotlin Native 的细节。

    接下来我们看这句:

    1. val vmValue = vm.pointed.pointed!!

    C 版本的定义 JavaVM 其实本身也是一个指针:

    1. typedef const struct JNIInvokeInterface* JavaVM;

    因此两个 pointed 的调用相当于获取到了 JNIInvokeInterface 这个结构体,于是后面我们就可以用它持有的函数指针进行获取 JNIEnv 的操作了:

    1. val result = vmValue.GetEnv!!(vm, envStorage.ptr.reinterpret(), JNI_VERSION_1_6)

    再稍微提一个事儿,那就是这些类型从 C 的角度映射过来,空类型安全自然是无法保证的,因此我们会见到各种 !! 的使用,这样实际上对于开发来讲非常不友好。因此理想的状况是,我们用 Kotlin Native 对 C 接口进行封装,将这些底层的工作按照 Kotlin 的风格进行转换,这样我们使用起来就会容易得多——官方的 AndroidNativeActivity 的例子当中提供了 JniBridge 及一系列的类其实就是做了这样一件事儿,只不过还不算太完整。

    接下来我们要实现动态绑定了:

    1. val jclass = env.FindClass!!(envStorage.value, "com/example/hellojni/HelloJni".cstr.ptr)

    2. val jniMethod = allocArray<JNINativeMethod>(1)

    3. jniMethod[0].fnPtr = staticCFunction(::sayHello2)

    4. jniMethod[0].name = "sayHello2".cstr.ptr

    5. jniMethod[0].signature = "()V".cstr.ptr

    6. env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 1)

    这里面也向大家展示了如何将 Kotlin 函数转为 C 的函数指针,总体来讲思路还是很简单的,毕竟我们只是照猫画虎。

    问题也是很显然的,如果你也尝试这样做了,一定被这些映射过来的接口函数的签名给搞晕过:

    1. public final var RegisterNatives: kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlinx.cinterop.CPointer<platform.android.JNIEnvVar /* = kotlinx.cinterop.CPointerVarOf<platform.android.JNIEnv /* = kotlinx.cinterop.CPointer<platform.android.JNINativeInterface> */> */>?, platform.android.jclass? /* = kotlinx.cinterop.CPointer<out kotlinx.cinterop.CPointed>? */, kotlinx.cinterop.CPointer<platform.android.JNINativeMethod>?, platform.android.jint /* = kotlin.Int */) -> platform.android.jint /* = kotlin.Int */>>? /* compiled code */

    这其实就是 RegisterNatives 这个函数指针的签名,它接受 JNIEnv 的值,jclass,以及一个 JNINativeMethod 结构体的数组和这个数组的长度作为参数,但我们点进去看源码或者看函数前面却需要看这么一大堆东西,直接晕菜。

    这其实也是目前 Kotlin Native 比较麻烦的问题之一:开发体验。尽管 1.0-Beta 出来之后,相比过去要好了许多,但开发体验似乎仍然有待提高,这其实也会直接影响开发者的涌入。

    简单来说,这篇文章没什么太大的技术含量,只是对上一篇文章的一个补充。

    本文涉及源码参见:hello-kni / https://github.com/enbandari/hello-kni


    转载请注明出处:微信公众号 Kotlin

    640?wx_fmt=jpeg

    展开全文
  • 解读 Kotlin/Native 技术预览版


    https://www.qingjingjie.com/blogs/48


    很高兴Kotlin在前两天发布了Kotlin/Native的Tech Preview版本。Kotlin/Native能把Kotlin编译成机器码,也就是C/C++、Go和Rust的层次,于是这个领域又添一位竞争对手。

    JetBrains是一家务实的公司,它家的各种IDE让人赞不绝口。它创造的Kotlin也是一门务实的编程语言,借鉴Java和Scala的精华,以实用性为导向。

    Kotlin的宣传语是“100%可与Java互操作”,事实证明确实如此,极低的学习成本和采用成本,极高的开发效率提升,在Android上已经火得不得了了,我在服务器上也用得很开心。现在Kotlin编译到机器码,不运行在JVM,就不能沿用Java的生态系统了,而是要融入系统编程的生态系统。Kotlin也的确是想做一个高性能的“胶水”。在这个领域,失去了JVM的庇佑,却得到了新的力量。

    Kotlin已经实现了编译到JavaScript,现在又编译到机器码,野心不可谓不大。它的实力能否匹配野心呢?从JetBrains的 博客 来看,他们对Kotlin/Native也是很务实的态度,所以这个项目是值得期待的。技术预览版来了,Alpha和Beta还会远吗?欢欣鼓舞!

    Kotlin/Native利用LLVM来编译到机器码,现在已支持4个平台:

    • Linux (暂时只是Ubuntu),
    • Mac OS
    • iOS
    • Raspberry Pi

    这意味着开发者们已经可以愉快地开始体验了!

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

    关于内存管理,现在的计划是在不同平台使用各自适合的内存管理技术,例如在服务器和桌面平台倾向于使用追踪式垃圾回收(tracing GC),而在iOS则倾向于使用iOS已有的ARC技术。在某些平台则可能手动管理内存。现在的Tech Preview版本只提供了一种附带循环检测的引用计数式内存管理,但并没有决定最终会怎样选择内存管理技术。

    这个版本基本上没做什么优化,所以在benchmark中可能会表现得不好。但是一定会努力优化,用实际数据说话,而不会拿理论来忽悠人。标准库和反射功能也还远未完成,但是愿意做这些东西已经让我觉得很良心了。

    博客中列出的未来计划包括:

    • 进军iOS
    • 进军嵌入式系统和物联网
    • 进军数据分析和科学计算
    • 进军服务端和微服务
    • 进军游戏开发

    进军iOS意味着要在移动端全制霸,而且还想进军这么多领域,似乎盘子有点太大了。我希望他们能把JavaScript先放下,毕竟很多语言都在这里折戟了,还记得当年的GWT吗?不如在Native层多多发力,先定一个小目标——抢Go语言的盘子。


    展开全文
  • Kotlin/Native尝试

    2019-03-16 17:47:55
    在官网看到Kotlin/Native已经达到1.0 Beta版于是就去尝试了一下,结果发现坑还是挺多的。首先Kotlin/JVM很多库是用不了的,这个已经猜到了。官网说已经预先导入了 POSIX、 gzip、 OpenGL、 Metal、 Foundation 等很...
        

    Kotlin/Native尝试

    在官网看到Kotlin/Native已经达到1.0 Beta版于是就去尝试了一下,结果发现坑还是挺多的。
    首先Kotlin/JVM很多库是用不了的,这个已经猜到了。官网说已经预先导入了 POSIX、 gzip、 OpenGL、 Metal、 Foundation 等很多其他的库,然后我就尝试了下基本的文件读写。和C还是有一点的差别的。如下:

    fun hello(): String = "Hello, Kotlin/Native!"
    fun letter() = "abcdefghigklmnopqrstuvwxyz"
    
    fun main(args: Array<String>) {
        val file = fopen("data.txt", "w")
        fprintf(file, "%s", hello())
        fprintf(file, "%s", "\n")
        fprintf(file, "%s", letter())
        fclose(file)
        println("write finished")
        val filer = fopen("data.txt", "r")
        val buf = ByteArray(255)
    //    fscanf(filer, "%s", buf.pin().addressOf(0))
        fgets(buf.pin().addressOf(0), 255, filer)
        fclose(filer)
        print(buf.stringFromUtf8())
        buf.pin().unpin()
        println("read finished")
        system("pause")
    }

    运行结果如下

    > Task :runProgram
    write finished
    Hello, Kotlin/Native!
    read finished
    Press any key to continue . . . 
    C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found
    
    > Task :runProgram FAILED

    令人郁闷的是提示C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found,虽然调用了buf.pin().unpin(),但依旧提示内存泄漏,也没有异常退出啊。

    如果是改成如下方式就不会提示错误了:

    fun hello(): String = "Hello, Kotlin/Native!"
    fun letter() = "abcdefghigklmnopqrstuvwxyz"
    
    fun main(args: Array<String>) {
        val file = fopen("data.txt", "w")
        fprintf(file, "%s", hello())
        fprintf(file, "%s", "\n")
        fprintf(file, "%s", letter())
        fclose(file)
        println("write finished")
        val filer = fopen("data.txt", "r")
        val buf = ByteArray(255)
    //    fscanf(filer, "%s", buf.pin().addressOf(0))
    //    fgets(buf.pin().addressOf(0), 255, filer)
    //    fclose(filer)
    //    print(buf.stringFromUtf8())
    //    buf.pin().unpin()
        buf.usePinned {
            fgets(it.addressOf(0), 255, filer)
            fclose(filer)
            print(buf.stringFromUtf8())
        }
        println("read finished")
        system("pause")
    }

    结果如下:

    > Task :runProgram
    write finished
    Hello, Kotlin/Native!
    read finished
    Press any key to continue . . . 
    
    BUILD SUCCESSFUL in 9s

    另外吐槽下,这么几行代码就要9s,是不是太慢了。

    随后又试了下开启pthread线程,但是pthread_create函数的第一个参数th: kotlinx.cinterop.CValuesRef<platform.posix.pthread_tVar>CValuesRef类型的变量怎么获得一直无解,难道只能通过继承获得?

    然后我在写文章的时候又发现只要这样写就可以了???

    fun main(args: Array<String>) {
        pthread_create(null, null, test(), null)
    }
    
    typealias func = kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlinx.cinterop.COpaquePointer?) -> kotlinx.cinterop.COpaquePointer?>>?
    
    fun test(): func {
        return staticCFunction<kotlinx.cinterop.COpaquePointer?, kotlinx.cinterop.COpaquePointer?> {
            println("run test")
            it
        }
    }

    结果如下:

    > Task :runProgram
    run test
    BUILD SUCCESSFUL in 8s
    展开全文
  • 当你还在死扣泛型语法的时候,别人的文章早就说了Kotlin/NativeKotlin1.3的新特性”。瞬间感觉自己out了,今天我们就说说这些时髦的东西,也许你能看到一些和别人不一样的东西哦。 前段时间你们的熊猫小哥哥(也...

    简述:
    今天我们来讲点Kotlin中比较时髦的东西,有的人可能会说:“不像你之前的风格啊,之前的文章不是一直在死扣语法以及语法糖背后秘密。当你还在死扣泛型语法的时候,别人的文章早就说了Kotlin/Native和Kotlin1.3的新特性”。瞬间感觉自己out了,今天我们就说说这些时髦的东西,也许你能看到一些和别人不一样的东西哦。

    前段时间你们的熊猫小哥哥(也就是我),由于对Kotlin过度热爱,一天偶然看到2018 JetBrains开发者日-Kotlin专场活动,脑袋一热,瞬间心动了,马上就买了门票和火车票去北京(第一次一个人去北京)参加活动了。因为看到有Kotlin中文社区两位大佬(这两位大佬是我一年多以前开始写Kotlin的时候就关注了他们)的演讲日程以及JetBrains资深布道师Hali的演讲,没有过多思考直接买票,不要怂就是干。最后顺便和Kotlin社区的大佬们面个基啥的,谢谢大佬们的热情款待。此次北京之行收获挺多的,有时候知道一些最新技术方向和动态会比你埋头闭门造车好的很多。

    因为在我的公众号上(Kotlin开发者联盟),有一些小伙伴希望我能从北京的开发者会上带点东西回来,所以总结了一下结合自己实际的开发,给大家带来以下几篇文章。

    • 1、Kotlin/Native1.0 Beta(尝鲜篇)
    • 2、Kotlin中1.3版本新特性都有哪些?
    • 3、Kotlin中的Coroutine(协程)在Android上应用(协程学前班篇)
    • 4、Ktor异步框架初体验(Ktor学前班篇)

    那么,今天就开始第一篇,看过一些大佬写关于Kotlin/ Native的文章,基本上都是翻译了Kotlin Blog的官网博客, 具体如何实践的还是比较少的。今天我不打算这么讲,既然今天的主题是时髦那就讲点有意思的东西。一起来看下今天提纲:

    一、重新认识Kotlin语言

    在开始之前,我觉得有必要一起重新来认识一下Kotlin这门语言,很多人一直都认为它不就是门JVM语言和Java、Scala一样都是跑在JVM虚拟机上。其实Kotlin并不仅仅是一门JVM语言,它的野心是真的大,JVM语言已经无法满足它的雄心壮志了。它是一门多平台的静态编译型语言,它可以用于JVM上(只不过在JVM层面比较出名而已,导致很多人都认为它是门JVM语言),实则它可以编译成JavaScipt运行在浏览器中也可以编译成IOS的可运行文件跑在LLVM上

    二、Kotlin/Native的基本介绍

    用官方的话来说Kotlin / Native是一种将Kotlin代码编译为本机二进制文件的技术,可以在没有虚拟机的情况下运行。它是基于LLVM的后端,用于Kotlin编译器和Kotlin标准库的本机实现

    Kotlin/Native目前支持以下平台:

    • — iOS (arm32, arm64, emulator x86_64)
    • — MacOS (x86_64)
    • — Android (arm32, arm64)
    • — Windows (mingw x86_64)
    • — Linux (x86_64, arm32, MIPS, MIPS little endian)
    • — WebAssembly (wasm32)

    为了更好说明Kotlin/Native能力,下面给出张官方的Kotlin/Native能力图:

    对于Kotlin/Native之前一直没有去玩过,只是经常听到社区小伙伴们说编译起来巨慢,感觉好时髦啊。抱着好奇心,并且也符合我们这篇文章时髦的主题,决定一步步带大家玩一玩。

    三、Kotlin/Native开发IOS HelloWorld

    1、需要准备的开发工具

    • AppCode 2018.1(建议下载最新版本,这里不是最新版本不过也能玩哈,最新版本应该到了2018.3)
    • Kotlin/Native Plugin 181.5087.34(注意: 插件和AppCode IDE的版本匹配问题,建议把IDE安装好,然后IDE搜索下载会默认给最佳匹配的插件版本的)
    • Xcode 9.2(注意: 这里Xcode版本需要AppCode版本匹配,否则会有问题的,不过不匹配的话IDE会有提示的,建议如果AppCode 2018.1(Xcode 9.2), AppCode 2018.3(Xcode 10.0))

    2、创建一个Kotlin/Native项目

    第一步: 选择左侧的Kotlin/Native, 并选择右侧的Sing View App with a Kotlin/Native Framework

    第二步: 填写项目名和包名,选择语言Swift(这里先以Swift为例)

    第三步: 最后finish即可创建完毕Kotlin/Native项目,创建完毕后项目结构如下

    4、运行Kotlin/Native项目

    如果你比较幸运跑起来的话,效果应该是在模拟器装一个APP并且起了一个空白页,终端上输出了"Hello from Kotlin!"的Log,类似这样:

    注意: 但是你是真题测试,而且Run顶部默认只有一个IOS Device选项的话,然后你又点了Run 说明而且会报如下错误

    这个问题是因为默认IOS Device选项是表示用真机调试哈,然后这边就需要一个IOS开发者账号。设置开发者账号的话,建议使用Xcode去打开该项目然后给该项目配置一个开发者账号。

    设置完毕Xcode后,AppCode会自动检测到刷新的。

    四、Kotlin/Native开发IOS 运行原理分析

    看到上面IOS HelloWorld项目运行起来,大家有没有思考一个问题,Kotlin的代码的代码是怎么在IOS设备上跑起来呢?

    实际上,在这背后使用了一些脚本和工具在默默支撑着整个项目的运行,如前所述,Kotlin / Native平台有自己的编译器,但每次想要构建项目时手动运行它明显不是高效的。 所以Kotlin团队了选择Gradle。Kotlin / Native使用Gradle构建工具在Xcode中自动完成Kotlin / Native的整个构建过程。在这里使用Gradle意味着开发人员可以利用其内部增量构建架构,只需构建和下载所需内容,从而节省开发人员的宝贵时间。

    如果,你还对上述有点疑问不妨一起来研究下Kotlin/Native项目中的构建参数脚本:

    • 打开构建脚本是需要在Xcode中打开的,具体可以参考如下图:

    通过以上项目可以分析到在Xcode中编译一个Kotlin/Native项目,实际上在执行一段shell脚本,并在shell脚本执行中gradlew命令来对Kotlin/Native编译,该脚本调用gradlew工具,该工具是Gradle Build System的一部分,并传递构建环境和调试选项。 然后调用一个konan gradle插件实现项目编译并输出xxx.kexe文件,最后并把它复制到iOS项目构建目录("$TARGET_BUILD_DIR/$EXECUTABLE_PATH")。

    最后来看下Supporting Files中的build.gradle构建文件,里面就引入了konan插件(Kotlin/Native编译插件), 有空的话建议可以深入研究下konan插件,这里其实也是比较浅显分析了下整个编译过程,如果深入研究konan插件源码的话,更能透过现象看到Kotlin/Native本质,这点才是最重要的。

    buildscript {
        ext.kotlin_version = '1.2.0'
        repositories {
            mavenCentral()
            maven {
                url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
            }
        }
    
        dependencies {
            classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.7"
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        }
    }
    
    apply plugin: 'kotlin'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile "org.jetbrains.kotlin:kotlin-stdlib"
    }
    
    apply plugin: 'konan'
    
    konan.targets = [
        'ios_arm64', 'ios_x64'
    ]
            
    konanArtifacts {
        program('KotlinNativeOC')
    }
            
    
    

    五、Kotlin/Native项目结构分析

    1、Kotlin/Native + Swift项目结构分析

    我们知道main函数是很多应用程序的入口,ios也不例外,在AppDelegate.swift中有@UIApplicationMain的注解,这里就是APP启动的入口。

    @UIApplicationMain //main函数注解入口,所以AppDelegate类相当于启动入口类
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?//默认加了UIWindow
    
    
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // KNFKotlinNativeFramework class is located in the framework that is generated during build.
        // If it is not resolved, try building for the device (not simulator) and reopening the project
        NSLog("%@", KNFKotlinNativeFramework().helloFromKotlin())//注意: 这里就是调用了Kotlin中的一个helloFromKotlin方法,并把返回值用Log打印出来,所以你会看到App启动的时候是有一段Log被打印出来
                      
        return true
        }
        ...
    }
    

    KotlinNativeFramework类

    class KotlinNativeFramework {
        fun helloFromKotlin() = "Hello from Kotlin!" //返回一个Hello from Kotlin!字符串
    }
    

    但是呢,有追求的程序员绝对不能允许跑出来的是一个空白页面,空白页面那还怎么装逼呢? 哈哈。在ViewController.swift中的viewDidLoad函数中加入一个文本(UILabel)。

    class ViewController: UIViewController {
        override func viewDidLoad() {
        super.viewDidLoad()
            let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 21))
            label.center = CGPoint(x: 160, y: 285)
            label.textAlignment = .center
            label.font = label.font.withSize(15)
            label.text = "Hello IOS, I'm from Kotlin/Native"
            view.addSubview(label)
        }
        override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
        }
    }
    

    最后重新run一遍,效果如下:

    2、Kotlin/Native + Objective C项目结构分析

    在IOS同事帮助下,进一步了解IOS APP启动基本知识,这将有助于我们接下来改造我们项目结构,使得它更加简单,完全可以删除额外的Swift代码,包括APP启动代理那块都交由Kotlin来完成。

    • 第一步: 先创建一个Kotlin/Native + OC 的项目,这里就不重复创建过程,直接把OC目录结构给出:

    • 第二步: 可以看到OC与Swift项目结构差不多哈,可以看到其中有几个重要的文件,main.m、AppDelegate.m、ViewController.m
      main.m APP启动入口,相当于main函数,先从main函数入手,然后一步步弄清整个启动流程。
    #import <UIKit/UIKit.h>
    #import "AppDelegate.h"
    
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));//这里也调用了AppDelegate类
        }
    
    }
    
    • 第三步: 然后转到AppDelegate.m,可以看到在didFinishLaunchingWithOptions函数中调用了KNFKotlinNativeFramework中的helloFromKotlin函数。
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // KNFKotlinNativeFramework class is located in the framework that is generated during build.
        // If it is not resolved, try building for the device (not simulator) and reopening the project
        NSLog(@"%@", [[[KNFKotlinNativeFramework alloc] init] helloFromKotlin]);//注意这里调用helloFromKotlin,并输出日志
                      
        return YES;
    }
    

    3、Kotlin/Native + Kotlin项目结构分析

    到这里很多人就会问了,看你上面说了那么并没有看到你Kotlin在做什么事,全是Swift和OC在做APP启动。现在就是告诉你Kotlin如何去替代它们做APP启动的事了。

    • 先新创建一个项目,这次创建的不再是Sing View App with a Kotlin/Native Framework, 而是一个Application项目。

    • 生成后的目录文件全是Kotlin文件,具体如下:

    • 生成的main.kt替代main.m,并设置对应启动的AppDelegate
    import kotlinx.cinterop.autoreleasepool
    import kotlinx.cinterop.cstr
    import kotlinx.cinterop.memScoped
    import kotlinx.cinterop.toCValues
    import platform.Foundation.NSStringFromClass
    import platform.UIKit.UIApplicationMain
    
    fun main(args: Array<String>) {
        memScoped {
            val argc = args.size + 1
            val argv = (arrayOf("konan") + args).map { it.cstr.getPointer(memScope) }.toCValues()
    
            autoreleasepool {
                UIApplicationMain(argc, argv, null, NSStringFromClass(AppDelegate))//注意: 在这里设置对应启动的AppDelegate
            }
        }
    }
    
    • 生成AppDelegate替代原来的AppDelegate.m,并且在内部设置好启动的Window.
    import kotlinx.cinterop.initBy
    import platform.Foundation.NSLog
    import platform.UIKit.*
    
    class AppDelegate : UIResponder(), UIApplicationDelegateProtocol {
        override fun init() = initBy(AppDelegate())
        private var _window: UIWindow? = null
        override fun window() = _window
        override fun setWindow(window: UIWindow?) { _window = window }
        override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {//监听APP启动完成,打印Log
            NSLog("this is launch from kotlin appDelegate")
            return true
    }
        companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta//注意:一定得有个companion object否则在main函数NSStringFromClass(AppDelegate)会报错
    }
    
    • 再生成一个ViewController,这个ViewController很类似Android中的Activity。
    import kotlinx.cinterop.*
    import platform.Foundation.*
    import platform.UIKit.*
    
    @ExportObjCClass
    class ViewController : UIViewController {
    
        constructor(aDecoder: NSCoder) : super(aDecoder)
        override fun initWithCoder(aDecoder: NSCoder) = initBy(ViewController(aDecoder))
    
        @ObjCOutlet
        lateinit var label: UILabel
    
        @ObjCOutlet
        lateinit var textField: UITextField
    
        @ObjCOutlet
        lateinit var button: UIButton
    
        @ObjCAction
        fun buttonPressed() {
            label.text = "Konan says: 'Hello, ${textField.text}!'"
        }
    }
    

    运行出来的效果如下:

    六、Kotlin/Native开发一个地图Demo

    1、IOS项目ViewController与组件绑定过程分析

    看到上面的运行Demo,大家有没有在思考一个问题IOS项目中的ViewController是怎么和UI组件绑定在一起的呢?我个人认为这个很重要,换句话说这就是IOS开发最基本的套路,如果这个都不弄明白的话,下面Demo开发就是云里雾里了,掌握了这个基本套路的话,作为一个Android开发者,你基本上就可以在IOS项目开发中任意折腾了。

    • 第一步: 在kotlin目录下新建一个KNMapViewController类,并且它去继承UIViewController以及实现MKMapViewDelegateProtocol接口,并重写viewDidLoad()函数。并且在viewDidLoad函数实现map地图基本配置。
    //导入Kotlin以与Objective-C和一些Cocoa Touch框架互操作。
    import kotlinx.cinterop.*
    import platform.CoreLocation.CLLocationCoordinate2DMake
    import platform.Foundation.*
    import platform.MapKit.MKCoordinateRegionMake
    import platform.MapKit.MKCoordinateSpanMake
    import platform.MapKit.MKMapView
    import platform.MapKit.MKMapViewDelegateProtocol
    import platform.UIKit.*
    
    @ExportObjCClass//注意: @ExportObjCClass注解有助于Kotlin创建一个在运行时可查找的类。
    class KNMapViewController: UIViewController, MKMapViewDelegateProtocol {
        @ObjCOutlet //注意: @ObjCOutlet注解很重要,主要是将mMapView属性设置为outlet。这允许您将Main.storyboard中的MKMapview链接到此属性。
        lateinit var mMapView: MKMapView
        constructor(aDecoder: NSCoder) : super(aDecoder)
        override fun initWithCoder(aDecoder: NSCoder) = initBy(KNMapViewController(aDecoder))
        override fun viewDidLoad() {
            super.viewDidLoad()
            val center = CLLocationCoordinate2DMake(32.07, 118.78)
            val span = MKCoordinateSpanMake(0.7, 0.7)
            val region = MKCoordinateRegionMake(center, span)
    
            with(mMapView) {
                delegate = this@KNMapViewController
                setRegion(region, true)
            }
        }
    }
    
    • 第二步: 用Xcode打开项目中的Main.storyboard,删除原来自动生成一些视图组件(如果你处于AppCode中开发项目,实际上直接在AppCode中双击Main.storyboard就会自动使用Xcode打开当前整个项目,并打开这个项目)

    • 第三步: 给当前空的视图绑定对应ViewController,这里是KNMapViewController

    • 第四步: 在当前空的视图中添加一个map view组件并且设置组件的约束条件。

    • 第五步: 右击组件MKMapView可以看到黑色对话框,里面Referencing Outlets还空的,说明当前ViewController没有和MKMapView组件绑定

    • 第六步: 配置outlet,这里说下AppCode很坑爹地方,需要手动去source code中手动配置outlet,选中main.storyboard右击open as 然后选择打开source code

    • 第七步: 在view和viewController结尾标签之间配置connection

      配置的code如下:
    <connections>
       <outlet property="mMapView" destination="dest id" id="generate id"/>
    </connections>
    <!--property属性值就是KNMapViewController中的mMapView变量名;destination属性值是一个map view标签中id(可以在subviews标签内的mapView标签中找到id), id属性则是自动生成的,可以按照格式自己之指定一个,只要不出现重复的id即可-->
    

    配置结果如下:

    • 第八步: 检验是否绑定成功, 回到main.stroyboard视图,右击组件查看黑色框是否出现如下绑定关系,出现了则说明配置成功。

    2、接着上述配置步骤,就可以回到AppCode中运行项目了


    以上的运行结果就说明了,我们Demo已经运行成功了。并且我已经把此次Kotlin/Native项目都放到了GitHub上,如果感兴趣的小伙伴可以clone下来玩一玩这个时髦的鬼东西。如果觉得对你有帮助,还请大佬们给个star。

    Kotlin/Native Demo GitHub

    七、Kotlin/Native开发体验分析

    Kotlin/Native目前还是处于1.0的beta,所以还是有很多的地方是不让人满意的。下面我总结这次Kotlin/Native开发体验的优缺点:

    优点:

    通过上述的几个例子,可以明显表明Kotlin/Native语言层面跨平台能力还是很强的,和OC,Swfit项目操作性也很强,该有的基本上都已经实现了,所以对于它后续发展还是非常值得关注的。据我了解到,Kotlin团队目前首要重心就是在Kotlin/Native这一块,希望他们能给我们带来更多关于Kotlin/Native的惊喜。

    缺点

    缺点还是有很多的:

    • 1、Kotlin/Native编译速度有点慢的惊人,不过运行速度还是不错的。
    • 2、AppCode中用Kotlin开发IOS项目,没有很强的代码提示,很多API导包都比较麻烦,对于初学者更是一脸懵逼。
    • 3、AppCode中的Kotlin/Native插件居然没提供右键创建Kotlin类、接口之类,包括官方也给出这是个问题不过提供一个fix方案。

    最后可以测试一下:

    • 4、AppCode中使用Kotlin开发IOS项目,配置outlet还得切换到在Xcode手工操作,希望后续改进。
    • 5、AppCode耗内存还大了,比AndroidStudio还厉害,多开了两个项目,基本就是每操作IDE就在转圈圈,希望后续改进。

    八、聊点关于Kotlin/Native和Flutter框架那点事

    1、简述:

    其实关于这个主题,我是不太想去说的,因为我不想引起编程语言界的口战。但是总有人喜欢去把Kotlin/Native和Flutter放在一起去做对比,因为他们好像都有一个共同点就是跨平台都能开发Android、IOS应用。并且在此次Jetbrains开发者日上就有参会嘉宾在会上问官方布道师Hali,Kotlin/Native和Flutter有什么不一样,优势在哪?

    2、提出个人观点:

    针对这个问题我说下个人的观点:

    首先,我个人是非常看好Flutter这个移动端跨平台框架,它能够完全抹平Android,IOS API层面的差异性。换句话说就是一个小白,也许他不懂Java,OC或Swift,他只要熟悉dart并且熟悉Flutter框架API就能开发出Android和IOS两个原生般体验的应用。确实很爽啊,无论站在公司节约人力和维护成本还是开发者技术成本角度考虑都是不错的选择。可是从另一角度可以想象下一旦形成这样的局面,很多新项目都是用dart开发,swift和oc语言对于新的开发者而言你会去用吗? 苹果爸爸貌似就会不开心了,这其中故事大家可以自己去想像了(当然也许未来不是我说的那样发展,纯属个人猜想的哈)。

    然后,我说下Kotlin/Native吧,其实Kotlin/Native和Flutter不是一个层面东西,不太很好做对比,Kotlin/Native更多的是语言编译器层面,而Flutter是框架层面。两者根本就不是一个世界的。Flutter是自己实现一套渲染机制和UI引擎,并且有着丰富Development API。而Kotlin/Native更多关注是编译器如何将kt源码编译成IOS LLVM可运行的二进制码,但是Kotlin/Native并没有像Flutter一样在API层面抹平平台差异性,而是
    在语言层面做了平台差异性抹平。也就是说你要开发IOS应用不仅会Kotlin/Native还得会IOS 应用开发Development Api. 这貌似Kotlin/Native稍逊Flutter一筹,但是想想也有道理,API层面抹平不是在语言层面职责,还是需要框架层面来做到这一点,说不定哪天Kotlin团队也造出一个类似Flutter的轮子呢。而且语言层面跨平台带来的还有一点好处就是共享代码,Android、IOS、前端都可以用Kotlin来实现,可以把它们拥有相同逻辑用Kotlin编写的代码放入一个common包中统一管理,并且Kotlin对多平台共享这块做了很好的支持,然后来自于不同平台可以共享这块通用的逻辑实现,不用各自平台去写一套了。

    3、给开发者抉择建议:

    通过上述观点,我们总结到对于开发者而言,学习一门新的技术实际上一般会存在两层隐形成本。一层是对新的开发语言的掌握,另一层则是对这门技术的Development Api的熟悉和掌握。那么Kotlin/Native就是为了磨平第一层开发语言的障碍。语言层面能做的也只能这样了,不像Flutter它是一个框架,它可以直接从API层面抹平。如果Flutter最终能推广起来,在热更新和热修复这块支持比现在的原生还好的话,并且你是一个初学者,那么它绝对是一个不错的选择。

    如果你是Kotlin开发者,你完全可以使用Kotlin/Native然后花一些时间熟悉下IOS的API也能把IOS应用玩起来。当然Kotlin/Native开发IOS应用只是其中一小部分,你完全用它去做更多有意义的事。

    如果你是移动开发者,建议两门技术都可以去尝试一下毕竟技多不压身,当然语言只是一门工具,最终靠还是计算机扎实的基础、分析需求的能力以及架构项目的功能能力。有了这些能力再加上一个高效的编程语言那么你就更加所向披靡了。

    Kotlin系列文章,欢迎查看:

    欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

    Kotlin系列文章,欢迎查看:

    Kotlin邂逅设计模式系列:

    数据结构与算法系列:

    翻译系列:

    原创系列:

    Effective Kotlin翻译系列

    实战系列:

    展开全文
  • 第16章 使用 Kotlin Native 最新上架!!!《 Kotlin极简教程》 陈光剑 (机械工业出版社) 可直接打开京东,淘宝,当当===> 搜索: Kotlin 极简教程 http://www.jianshu.com/p/35b487734339 不得不说 JetBrains ...

    第16章 使用 Kotlin Native

    最新上架!!!《 Kotlin极简教程》 陈光剑 (机械工业出版社)
    可直接打开京东,淘宝,当当===> 搜索: Kotlin 极简教程
    http://www.jianshu.com/p/35b487734339

    不得不说 JetBrains 是一家务实的公司,各种IDE让人赞不绝口,用起来也是相当溜。同样的,诞生自 JetBrains 的 Kotlin 也是一门务实的编程语言,Kotlin以工程实用性为导向,充分借鉴了Java, Scala, Groovy, C#, Gosu, JavaScript, Swift等等语言的精华,让我们写起代码来可谓是相当优雅却又不失工程质量与效率。Kotlin Native能把 Kotlin代码直接编译成机器码,也就是站在了跟 C/C++、Go和Rust的同一个层次,于是这个领域又添一位竞争对手。

    在前面的所有章节中,我们使用的 Kotlin 都是基于 JVM 的运行环境。本章我们将从 JVM 的运行环境中离开,走向直接编译生成原生机器码的系统编程的生态系统:Kotlin Native 。

    16.1 Kotlin Native 简介

    Kotlin Native利用LLVM来编译到机器码。Kotlin Native 主要是基于 LLVM后端编译器(Backend Compiler)来生成本地机器码。

    Kotlin Native 的设计初衷是为了支持在非JVM虚拟机平台环境的编程,如 ios、嵌入式平台等。同时支持与 C 互操作。

    16.1.1 LLVM

    LLVM最初是Low Level Virtual Machine的缩写,定位是一个虚拟机,但是是比较底层的虚拟机。LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。

    LLVM的出现正是为了解决编译器代码重用的问题,LLVM一上来就站在比较高的角度,制定了LLVM IR这一中间代码表示语言。LLVM IR充分考虑了各种应用场景,例如在IDE中调用LLVM进行实时的代码语法检查,对静态语言、动态语言的编译、优化等。

    16.1.2 支持平台

    Kotlin Native现在已支持以下平台:

    平台名称 target 配置
    Linux linux
    Mac OS macbook
    Windows mingw
    Android arm32 android_arm32
    Android arm64 android_arm64
    iOS iphone
    Raspberry Pi raspberrypi

    这意味着我们可以在这些平台上愉快地开始体验了!目前Kotlin Native 已经发布的最新预发布版本是 v0.3 。

    16.1.3 解释型语言与编译型语言

    编译型语言,是在程序执行之前有一个单独的编译过程,将程序翻译成机器语言,以后执行这个程序的时候,就不用再进行翻译了。例如,C/C++ 等都是编译型语言。

    解释型语言,是在运行的时候将程序翻译成机器语言,所以运行速度相对于编译型语言要慢。例如,Java,C#等都是解释型语言。

    虽然Java程序在运行之前也有一个编译过程,但是并不是将程序编译成机器语言,而是将它编译成字节码(可以理解为一个中间语言)。在运行的时候,由JVM将字节码再翻译成机器语言。

    16.2 快速开始 Hello World

    16.2.1 运行环境准备

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

    螢幕快照 2017-07-29 13.23.30.png
    螢幕快照 2017-07-29 13.23.30.png

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

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

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

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

    16.2.2新建 Gradle 工程

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

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

    螢幕快照 2017-07-29 13.35.12.png
    螢幕快照 2017-07-29 13.35.12.png

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

    螢幕快照 2017-07-29 13.36.01.png
    螢幕快照 2017-07-29 13.36.01.png

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

    螢幕快照 2017-07-29 13.36.47.png
    螢幕快照 2017-07-29 13.36.47.png

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

    螢幕快照 2017-07-29 13.37.11.png
    螢幕快照 2017-07-29 13.37.11.png

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

    螢幕快照 2017-07-29 13.37.23.png
    螢幕快照 2017-07-29 13.37.23.png

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

    螢幕快照 2017-07-29 13.38.50.png
    螢幕快照 2017-07-29 13.38.50.png

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

    16.2.3 源代码目录

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

    src
    ├── c
    │   ├── cn_kotlinor.c
    │   ├── cn_kotlinor.h
    └── kotlin
        └── main.kt
    
    

    16.2.4 C 代码文件

    cn_kotlinor.h

    C头文件中声明如下

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

    我们简单声明了3个函数。

    cn_kotlinor.c

    C 源代码文件内容如下

    #include "cn_kotlinor.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);
    }
    
    int fib(int n){
        printf("[C]calc fibonacci: %d\n", n);
        if(n==1||n==2) return 1;
        return fib(n-1) + fib(n-2);
    }
    
    

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

    16.2.5 Kotlin 代码文件

    main.kt 文件内容如下

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

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

    16.2.6 konan插件配置

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

    buildscript {
        repositories {
            mavenCentral()
            maven {
                url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
            }
        }
        dependencies {
            classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.3"
        }
    }
    

    这里我们添加了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

    apply plugin: 'konan' 
    

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

    16.2.7 konanInterop 互操作配置

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

    konanInterop 主要用来配置 Kotlin 调用 C 的接口。konanInterop 的配置是由konan 插件API中的 KonanInteropTask.kt来处理的(这个类的源码在: https://github.com/JetBrains/kotlin-native/blob/master/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanInteropTask.kt)。

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

    关于konanInterop的配置选项有

      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>
           }
     
           // TODO: add configuration for konan compiler
     }
    

    我们简要说明如下表所示

    配置项 功能说明
    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

    headers=cn_kotlinor.h
    compilerOpts=-Isrc/c
    

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

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

    关于这个配置文件的解析原理可以参考 KonanPlugin.kt 文件的源码(https://github.com/JetBrains/kotlin-native/blob/master/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanPlugin.kt)。

    16.2.8 konanArtifacts 配置

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

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

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

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

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

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

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

     konanArtifacts {
     
           artifactName1 {
     
               inputFiles "files" "to" "be" "compiled"
     
               outputDir "path/to/output/dir"
     
               library "path/to/library"
               library File("Library")
     
               nativeLibrary "path/to/library"
               nativeLibrary File("Library")
     
               noStdLib
               produce "library"|"program"|"bitcode"
               enableOptimization
     
               linkerOpts "linker" "args"
               target "target"
     
               languageVersion "version"
               apiVersion "version"
     
         }
          artifactName2 {
     
               extends artifactName1
    
               inputDir "someDir"
               outputDir "someDir"
          }
     
       }
    

    konan 编译任务配置处理类是KonanCompileTask.kt (https://github.com/JetBrains/kotlin-native/blob/master/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanCompileTask.kt)。

    16.2.9 完整的 build.gradle 配置

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

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

    提示:关于konan 插件详细配置文档:Gradle DSL https://github.com/JetBrains/kotlin-native/blob/master/GRADLE_PLUGIN.md

    16.2.10 使用 clang 编译 C 代码

    为了实用性,我们新建一个 shell 脚本 kclang.sh 来简化 clang 编译的命令行输入参数

    #!/usr/bin/env bash
    clang -std=c99 -c $1 -o $2 -emit-llvm
    

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

     kclang.sh cn_kotlinor.c cn_kotlinor.bc
    

    我们将得到一个 cn_kotlinor.bc 库文件。

    提示:clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/Objective-C++编译器。它与GNU C语言规范几乎完全兼容。更多关于 clang 的内容可参考 : http://clang.llvm.org/docs/index.html

    16.2.11 配置 konan 编译器主目录

    最后,在执行 Gradle 构建之前,我们还需要指定konan 编译器主目录。我们在工程根目录下面新建 gradle.properties 这个属性配置文件,内容如下

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

    16.2.12 执行构建操作

    我们直接在 IDEA 右侧的 Gradle 工具栏点击Tasks ->build -> build 命令执行构建操作

    螢幕快照 2017-07-30 03.42.19.png
    螢幕快照 2017-07-30 03.42.19.png

    我们会看到终端输出

    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'.
    
    

    构建完成之后,会在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
    
    

    其中在 ckotlinor.kt中,我们可以看出 konan 编译器还为我们生成了 C 代码对应的 Kotlin 的接口

    @file:Suppress("UNUSED_EXPRESSION", "UNUSED_VARIABLE")
    package ckotlinor
    
    import konan.SymbolName
    import kotlinx.cinterop.*
    
    fun printHello(): Unit {
        val res = kni_printHello()
        return res
    }
    
    @SymbolName("ckotlinor_kni_printHello")
    private external fun kni_printHello(): Unit
    
    fun factorial(n: Int): Int {
        val _n = n
        val res = kni_factorial(_n)
        return res
    }
    
    @SymbolName("ckotlinor_kni_factorial")
    private external fun kni_factorial(n: Int): Int
    
    fun fib(n: Int): Int {
        val _n = n
        val res = kni_fib(_n)
        return res
    }
    
    @SymbolName("ckotlinor_kni_fib")
    private external fun kni_fib(n: Int): Int
    
    
    

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

    16.2.12 执行 kexe 应用程序

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

    chatper16_kotlin_native_helloworld$ build/konan/bin/KotlinorApp.kexe  
    

    我们可以看到如下输出:

    [C]HelloWorld
    [C]calc factorial: 1
    [C]calc factorial: 0
    [C]calc factorial: 2
    ...
    [C]calc factorial: 2
    [C]calc factorial: 1
    [C]calc factorial: 0
    1
    2
    6
    24
    120
    720
    5040
    [C]calc fibonacci: 1
    [C]calc fibonacci: 2
    [C]calc fibonacci: 3
    ...
    [C]calc fibonacci: 3
    [C]calc fibonacci: 2
    [C]calc fibonacci: 1
    1
    1
    2
    3
    5
    8
    13
    
    

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

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

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

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

    16.3 Kotlin Native 编译器 konan 简介

    本小节我们简单介绍一下Kotlin Native 编译器的相关内容(主要以 Mac OS 平台示例)。

    bin目录

    bin目录下面是执行命令行

    cinterop       klib           konanc         kotlinc        kotlinc-native  run_konan
    

    run_konan 是真正的入口 shell,它的执行逻辑是

    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
    ...
    java_opts=(-ea \
                -Xmx3G \
                "-Djava.library.path=${NATIVE_LIB}" \
                "-Dkonan.home=${KONAN_HOME}" \
               -Dfile.encoding=UTF-8)
    
    KONAN_JAR="${KONAN_HOME}/konan/lib/backend.native.jar"
    KOTLIN_JAR="${KONAN_HOME}/konan/lib/kotlin-compiler.jar"
    STUB_GENERATOR_JAR="${KONAN_HOME}/konan/lib/StubGenerator.jar"
    INTEROP_INDEXER_JAR="${KONAN_HOME}/konan/lib/Indexer.jar"
    INTEROP_JAR="${KONAN_HOME}/konan/lib/Runtime.jar"
    HELPERS_JAR="${KONAN_HOME}/konan/lib/helpers.jar"
    KLIB_JAR="${KONAN_HOME}/konan/lib/klib.jar"
    UTILITIES_JAR="${KONAN_HOME}/konan/lib/utilities.jar"
    KONAN_CLASSPATH="$KOTLIN_JAR:$INTEROP_JAR:$STUB_GENERATOR_JAR:$INTEROP_INDEXER_JAR:$KONAN_JAR:$HELPERS_JAR:$KLIB_JAR:$UTILITIES_JAR"
    TOOL_CLASS=org.jetbrains.kotlin.cli.utilities.MainKt
    
    LIBCLANG_DISABLE_CRASH_RECOVERY=1 \
    $TIMECMD "$JAVACMD" "${java_opts[@]}" "${java_args[@]}" -cp "$KONAN_CLASSPATH" "$TOOL_CLASS" "$TOOL_NAME" "${konan_args[@]}"
    

    我们可以看出,Kotlin Native 编译器 konan 的运行环境还是在 JVM 上,但是它生成的机器码的可执行程序是直接运行在对应的平台系统上(直接编译成机器语言)。

    konan目录

    konan目录是 Kotlin Native 编译器的核心实现部分。目录结构如下:

    kotlin-native-macos-0.3$ tree konan
    konan/
    ├── konan.properties
    ├── lib
    │   ├── Indexer.jar
    │   ├── Runtime.jar
    │   ├── StubGenerator.jar
    │   ├── backend.native.jar
    │   ├── callbacks
    │   │   └── shared
    │   │       └── libcallbacks.dylib
    │   ├── clangstubs
    │   │   └── shared
    │   │       └── libclangstubs.dylib
    │   ├── helpers.jar
    │   ├── klib.jar
    │   ├── kotlin-compiler.jar
    │   ├── protobuf-java-2.6.1.jar
    │   └── utilities.jar
    └── nativelib
        ├── libcallbacks.dylib
        ├── libclangstubs.dylib
        ├── libllvmstubs.dylib
        └── liborgjetbrainskotlinbackendkonanhashstubs.dylib
    
    6 directories, 16 files
    

    我们可以看到在 run_konan 命令行 shell 中依赖了上面的这些 jar 包。上面的目录文件是 Mac OS 平台上的。

    对应的 Linux 平台上的konan目录文件如下

    kotlin-native-linux-0.3$ tree konan
    konan
    ├── konan.properties
    ├── lib
    │   ├── Indexer.jar
    │   ├── Runtime.jar
    │   ├── StubGenerator.jar
    │   ├── backend.native.jar
    │   ├── callbacks
    │   │   └── shared
    │   │       └── libcallbacks.so
    │   ├── clangstubs
    │   │   └── shared
    │   │       └── libclangstubs.so
    │   ├── helpers.jar
    │   ├── klib.jar
    │   ├── kotlin-compiler.jar
    │   ├── protobuf-java-2.6.1.jar
    │   └── utilities.jar
    └── nativelib
        ├── libcallbacks.so
        ├── libclangstubs.so
        ├── libllvmstubs.so
        └── liborgjetbrainskotlinbackendkonanhashstubs.so
    
    6 directories, 16 files
    

    Windows 平台上的 konan 目录文件如下

    kotlin-native-windows-0.3$ tree konan
    konan
    ├── konan.properties
    ├── lib
    │   ├── Indexer.jar
    │   ├── Runtime.jar
    │   ├── StubGenerator.jar
    │   ├── backend.native.jar
    │   ├── callbacks
    │   │   └── shared
    │   │       └── callbacks.dll
    │   ├── clangstubs
    │   │   └── shared
    │   │       └── clangstubs.dll
    │   ├── helpers.jar
    │   ├── klib.jar
    │   ├── kotlin-compiler.jar
    │   ├── protobuf-java-2.6.1.jar
    │   └── utilities.jar
    └── nativelib
        ├── callbacks.dll
        ├── clangstubs.dll
        ├── llvmstubs.dll
        └── orgjetbrainskotlinbackendkonanhashstubs.dll
    
    6 directories, 16 files
    
    

    klib 目录

    klib 目录下是 Kotlin 的标准库的关联元数据文件以及 Kotlin Native 针对各个目标平台的 bc 文件

    kotlin-native-macos-0.3$ tree klib
    klib/
    └── stdlib
        ├── linkdata
        │   ├── module
        │   ├── package_konan
        │   ├── package_konan.internal
        │   ├── package_kotlin
        │   ├── package_kotlin.annotation
        │   ├── package_kotlin.collections
        │   ├── package_kotlin.comparisons
        │   ├── package_kotlin.coroutines
        │   ├── package_kotlin.coroutines.experimental
        │   ├── package_kotlin.coroutines.experimental.intrinsics
        │   ├── package_kotlin.experimental
        │   ├── package_kotlin.internal
        │   ├── package_kotlin.io
        │   ├── package_kotlin.properties
        │   ├── package_kotlin.ranges
        │   ├── package_kotlin.reflect
        │   ├── package_kotlin.sequences
        │   ├── package_kotlin.text
        │   ├── package_kotlin.text.regex
        │   ├── package_kotlin.util
        │   ├── package_kotlinx
        │   ├── package_kotlinx.cinterop
        │   └── root_package
        ├── manifest
        ├── resources
        └── targets
            ├── android_arm32
            │   ├── kotlin
            │   │   └── program.kt.bc
            │   └── native
            │       ├── launcher.bc
            │       ├── runtime.bc
            │       └── start.bc
            ├── android_arm64
            │   ├── kotlin
            │   │   └── program.kt.bc
            │   └── native
            │       ├── launcher.bc
            │       ├── runtime.bc
            │       └── start.bc
            ├── iphone
            │   ├── kotlin
            │   │   └── program.kt.bc
            │   └── native
            │       ├── launcher.bc
            │       ├── runtime.bc
            │       ├── start.bc
            │       ├── start.kt.bc
            │       └── stdlib.kt.bc
            └── macbook
                ├── kotlin
                │   └── program.kt.bc
                └── native
                    ├── launcher.bc
                    ├── runtime.bc
                    └── start.bc
    
    16 directories, 42 files
    

    上面的目录是 kotlin-native-macos-0.3 平台的版本。我们可以看出,在Mac OS上,我们可以使用 Kotlin Native 编译android_arm32、android_arm64、iphone、macbook等目标平台的机器码可执行的程序。

    另外,对应的 Linux 平台的目录文件如下

    kotlin-native-linux-0.3$ tree klib
    klib/
    └── stdlib
        ├── linkdata
        │   ├── module
        │   ├── package_konan
        │   ├── package_konan.internal
        │   ├── package_kotlin
        │   ├── package_kotlin.annotation
        │   ├── package_kotlin.collections
        │   ├── package_kotlin.comparisons
        │   ├── package_kotlin.coroutines
        │   ├── package_kotlin.coroutines.experimental
        │   ├── package_kotlin.coroutines.experimental.intrinsics
        │   ├── package_kotlin.experimental
        │   ├── package_kotlin.internal
        │   ├── package_kotlin.io
        │   ├── package_kotlin.properties
        │   ├── package_kotlin.ranges
        │   ├── package_kotlin.reflect
        │   ├── package_kotlin.sequences
        │   ├── package_kotlin.text
        │   ├── package_kotlin.text.regex
        │   ├── package_kotlin.util
        │   ├── package_kotlinx
        │   ├── package_kotlinx.cinterop
        │   └── root_package
        ├── manifest
        ├── resources
        └── targets
            ├── android_arm32
            │   ├── kotlin
            │   │   └── program.kt.bc
            │   └── native
            │       ├── launcher.bc
            │       ├── runtime.bc
            │       └── start.bc
            ├── android_arm64
            │   ├── kotlin
            │   │   └── program.kt.bc
            │   └── native
            │       ├── launcher.bc
            │       ├── runtime.bc
            │       └── start.bc
            ├── linux
            │   ├── kotlin
            │   │   └── program.kt.bc
            │   └── native
            │       ├── launcher.bc
            │       ├── runtime.bc
            │       └── start.bc
            └── raspberrypi
                ├── kotlin
                │   └── program.kt.bc
                └── native
                    ├── launcher.bc
                    ├── runtime.bc
                    ├── start.bc
                    ├── start.kt.bc
                    └── stdlib.kt.bc
    
    16 directories, 42 files
    

    也就是说我们可以在 Linux 平台上编译android_arm32、android_arm64、linux、raspberrypi等平台上的目标程序。

    对应Windows 平台的如下

    kotlin-native-windows-0.3$ tree klib
    klib/
    └── stdlib
        ├── linkdata
        │   ├── module
        │   ├── package_konan
        │   ├── package_konan.internal
        │   ├── package_kotlin
        │   ├── package_kotlin.annotation
        │   ├── package_kotlin.collections
        │   ├── package_kotlin.comparisons
        │   ├── package_kotlin.coroutines
        │   ├── package_kotlin.coroutines.experimental
        │   ├── package_kotlin.coroutines.experimental.intrinsics
        │   ├── package_kotlin.experimental
        │   ├── package_kotlin.internal
        │   ├── package_kotlin.io
        │   ├── package_kotlin.properties
        │   ├── package_kotlin.ranges
        │   ├── package_kotlin.reflect
        │   ├── package_kotlin.sequences
        │   ├── package_kotlin.text
        │   ├── package_kotlin.text.regex
        │   ├── package_kotlin.util
        │   ├── package_kotlinx
        │   ├── package_kotlinx.cinterop
        │   └── root_package
        ├── manifest
        ├── mingw
        │   ├── kotlin
        │   │   └── program.kt.bc
        │   └── native
        │       ├── launcher.bc
        │       ├── runtime.bc
        │       └── start.bc
        ├── resources
        └── targets
            └── mingw
                ├── kotlin
                │   └── program.kt.bc
                └── native
                    ├── launcher.bc
                    ├── runtime.bc
                    └── start.bc
    
    10 directories, 32 files
    

    在 Windows 平台中,Kotlin Native 使用的是 mingw 库来实现的。目前,在 V0.3预发布版本,我们在 Windows 平台上可以体验的东西比较少,像 Android,iOS,Raspberrypi都还不支持。

    提示:MinGW,是Minimalist GNUfor Windows的缩写。它是一个可自由使用和自由发布的Windows特定头文件和使用GNU工具集导入库的集合,允许你在GNU/Linux和Windows平台生成本地的Windows程序而不需要第三方C运行时(C Runtime)库。MinGW 是一组包含文件和端口库,其功能是允许控制台模式的程序使用微软的标准C运行时(C Runtime)库(MSVCRT.DLL),该库在所有的 NT OS 上有效,在所有的 Windows 95发行版以上的 Windows OS 有效,使用基本运行时,你可以使用 GCC 写控制台模式的符合美国标准化组织(ANSI)程序,可以使用微软提供的 C 运行时(C Runtime)扩展,与基本运行时相结合,就可以有充分的权利既使用 CRT(C Runtime)又使用 WindowsAPI功能。

    samples目录

    samples目录下面是官方给出的一些实例。关于这些实例的文档介绍以及源码工程是: https://github.com/JetBrains/kotlin-native/tree/master/samples 。想更加深入了解学习的同学可以参考。

    本章小结

    本章工程源码: https://github.com/EasyKotlin/chatper16_kotlin_native_helloworld

    现在我们可以把 Kotlin 像C 一样地直接编译成的机器码来运行,这样在 C 语言出现的地方(例如应用于嵌入式等对性能要求比较高的场景),Kotlin 也来了。Kotlin 将会在嵌入式系统和物联网、数据分析和科学计算、游戏开发、服务端开发和微服务等领域持续发力。

    Kotlin 整个语言的架构不可谓不宏大:上的了云端(服务端程序),下的了手机端( Kotlin / Native ),写的了前端(JS,HTML DSL 等),嵌的了冰箱(Kotlin Native)。Kotlin 俨然已成为一门擅长多个领域的语言了。

    在互联网领域,目前在Web服务端应用开发、Android移动端开发是Kotlin 最活跃的领域。 Kotlin 将会越来越多地进入 Java 程序员们的视野, Java 程序员们会逐渐爱上 Kotlin 。

    未来的可能性有很多。但是真正的未来还是要我们去创造。

    展开全文
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • Android JNI中值/方法的传递与调用一级目录二级目录三级目录 按照习惯,我还是会用Kotlin做示范。 一级目录 二级目录 三级目录
  • 谷歌在今年的 I/O 大会上宣布,Kotlin 编程语言现在是 Android 应用程序开发人员的首选语言(谷歌宣布 Kotlin 成为安卓开发首选)。...版本更新的主要范围为 Kotlin/Native、KAPT 的性能优化以及对 ...
  • image ...在这次更新中,协程(Coroutines)特性已经稳定,它使得非阻塞代码易于读写,Kotlin 1.3 还带来了 Kotlin / Native Beta,它可将 Kotlin 代码直接编译为本机二进制文件,此外,Kotlin 的...
  • Kotlin 和 Swift, 两大新宠! 借 ReactNative 熟悉下 kotlin 的用法,不料掉坑里面了.昨晚花了大半夜,趁这会儿思路清晰,把涉及到的一些关键信息,迅速整理下.
  • 一、今天在创建了一个Kotlin+Spring的项目,结果启动报错 org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server....
  • 简述: 关注我的Kotlin浅谈系列文章的小伙伴就知道关于Kotlin语法篇的内容已经发布了一些。然后就会有小伙伴问了一直都在讲语法是否来一波实战了,毕竟一切一切的学习都是为了解决实际问题的,所以准备来一波Kotlin...
  • Kotlin是什么?

    2018-08-06 23:17:52
    Kotlin是一门可以运行在Java虚拟机,Android,浏览器上的静态语言。 ...Kotlin-Native 本地执行程序 package fenglei.demo /*  Hello World  */ fun main(args: Array&lt;String&gt;)...
  • 上接上篇文章,今天我们来讲点Kotlin 1.3版本中比较时髦的东西,那么,今天就开始第二篇,看过一些大佬写关于Kotlin 1.3版本新特性的文章,基本上都是翻译了Kotlin Blog的官网博客。今天我不打算这么讲,既然今天的...
  • 从零开始学 Kotlin

    2019-07-05 10:04:13
    这是一门 Kotlin 语言从零基础到提高的教程,主要内容包括:Kotlin 语法基础、数据类型、字符串、运算符、程序流程控制、函数、高阶函数、Lambda 表达式、函数式编程 API、面向对象、数组、集合、协程、Kotlin 与 ...
1 2 3 4 5 ... 20
收藏数 893
精华内容 357
关键字:

1.0 kotlin native