码农的世界互联网科技Kotlin编程

[Kotlin/Native] 创建全平台动态库

2019-05-28  本文已影响51人  何晓杰Dev

本来是想写一下用 Kotlin/Native 玩 JNI 的,同时手里有一些 Android JNI 项目试图做一个移植,研究了一下之后发现,Kotlin/Native 的跨平台库实在是太强大了,除了喊 666 也没别的话好说,于是干脆搞个大的,把全平台的库全部盘一遍。

首先要说一下,Kotlin/Native 体系下有一个很牛逼的命令叫做 konan,社区里也有人称它为柯南,这个全平台具体能全到什么程度,还要看柯南的。

$ konanc -list-targets
macos_x64:                    (default) macbook, macos, imac
ios_arm32:                    iphone32
ios_arm64:                    iphone, ipad, ios
ios_x64:                      iphone_sim
linux_x64:                    linux
linux_arm32_hfp:              raspberrypi
android_arm32:                          
android_arm64:                          
wasm32:      

除此之外,Kotlin/Native 原生支持 JSJVM 的编译,不被列在这个表内。另外,这个列表并非全部平台,有一些编译目标如 mingwX64 只能在 Windows 下进行,因此在 Mac 上的 konan 无法列出这些目标。

知道这些后,我们就可以按自己的需要来添加编译平台,这里需要注意的是,每个平台支持的编译方式都是有差异的,不能一概而论,下面我列了个表,来帮助大家写编译脚本:

平台 target 名称 编译目标 特殊选项
js js moduleKind [可选 amd, commonjs, umd]
sourceMap [可选 true, false]
jvm jvm jvmTarget
iOS iosArm64 framework embedBitcode [可选 bitcode]
staticLib
iosArm32 framework embedBitcode [可选 bitcode]
staticLib
iosX64 framework embedBitcode [可选 bitcode]
staticLib
Android androidNativeArm64 executable entryPoint
sharedLib
staticLib
androidNativeArm32 executable entryPoint
sharedLib
staticLib
Mac macosX64 executable entryPoint
sharedLib
staticLib
framework embedBitcode [可选 bitcode]
Linux linuxX64 executable entryPoint
sharedLib
staticLib
linuxMipsel32 executable entryPoint
sharedLib
staticLib
linuxMips32 executable entryPoint
sharedLib
staticLib
Windows mingwX64 executable entryPoint
sharedLib
staticLib
WebAssembly wasm32 executable entryPoint
Raspberry Pi linuxArm32Hfp executable entryPoint
sharedLib
staticLib

这个表怎么用呢?比如说要针对 iOS Arm64 编译一个 bitcode 的 framework,参考上表可以这样写脚本:

kotlin {
    iosArm64("ios64") {
        binaries {
            framework {
                embedBitcode "bitcode"
            }
        }
    }
}

同样的,如果要针对 Android Arm64 编译 JNI 库和对应的静态库,只需要这样写:

kotlin {
    androidNativeArm64("android64") {
        binaries {
            sharedLib { }
            staticLib { }
        }
    }
}

下面来说一下使用动态库的问题,对于通常的 C/C++ 库来说,Kotlin 都可以简单的通过 cinterop 来引入,官方也早就相关的文档来说明(点击查阅),同样的,也可以在 C/C++ 里使用 Kotlin 的库,官方文档也说明了这一点(点击查阅)。

在这里有一个问题,如官方文档所述,在实际应用中为了一个函数去写一大堆代码显然是不合适的,这里贴来官方的例子比较一下:

#include "libnative_api.h"
#include "stdio.h"

int main(int argc, char** argv) {
  //obtain reference for calling Kotlin/Native functions
  libnative_ExportedSymbols* lib = libnative_symbols();
... ...
  //use C and Kotlin/Native strings
  const char* str = "Hello from Native!";
  const char* response = lib->kotlin.root.example.strings(str);
  printf("in: %s\nout:%s\n", str, response);
  lib->DisposeString(response);
... ...
  return 0;
}

如果我想直接使用里面的 strings() 方法要怎么办呢?其实是可以使用命名注解的:

package example
... ...
@CName("strings")
fun strings(str: String) : String? {
  return "That is '$str' from C"
}
... ...

注意此处的 @CName() 对应的名称,就是最终导出的名称,所以我们就可以用简单的办法来访问了:

#include "libnative_api.h"
#include "stdio.h"

int main(int argc, char** argv) {
  //obtain reference for calling Kotlin/Native functions
  // libnative_ExportedSymbols* lib = libnative_symbols();
... ...
  //use C and Kotlin/Native strings
  const char* str = "Hello from Native!";
  // const char* response = lib->kotlin.root.example.strings(str);
  const char* response = strings(str);
  printf("in: %s\nout:%s\n", str, response);
  // lib->DisposeString(response);
... ...
  return 0;
}

通过同样的方法,我们可以声明 JNI 的导出函数:

@CName("Java_com_rarnu_sample_NativeAPI_sayHello")
fun sayHello(env: CPointer<JNIEnvVar>, thiz: jobject) { ... ... }

另外,全网搜索 Kotlin 调用自己的库未果,而且经过一系列常规的尝试后发现两个大家都遇到了的问题(第一个第二个),显然不可能往这些方向继续进行。

其实 Kotlin 要使用自己的库并没有那么麻烦,只不过设计思路有些不同,因为有一个很神奇的中间层叫 klib,通过引用 klib 就可以

$ konanc sample.kt -p library -o sample

编译库文件源码,可以得到一个 sample.klib 把它放到项目里,然后直接引用文件即可:

sourceSets {
    ... ...
    macosMain {
        dependencies {
            implementation files('sample.klib')
        }
    }
}

这样一来,这个 klib 就可以正确的代码中引用到了。

上一篇下一篇

猜你喜欢

热点阅读