[Kotlin/Native] 创建全平台动态库
本来是想写一下用 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 原生支持 JS
和 JVM
的编译,不被列在这个表内。另外,这个列表并非全部平台,有一些编译目标如 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
就可以正确的代码中引用到了。