本來是想寫一下用 Kotlin/Native 玩 JNI 的,同時手里有一些 Android JNI 項目試圖做一個移植,研究了一下之后發(fā)現(xiàn),Kotlin/Native 的跨平臺庫實在是太強大了,除了喊 666 也沒別的話好說,于是干脆搞個大的,把全平臺的庫全部盤一遍。
首先要說一下,Kotlin/Native 體系下有一個很牛逼的命令叫做 konan,社區(qū)里也有人稱它為柯南,這個全平臺具體能全到什么程度,還要看柯南的。
$ 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 的編譯,不被列在這個表內(nèi)。另外,這個列表并非全部平臺,有一些編譯目標如 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 庫和對應的靜態(tài)庫,只需要這樣寫:
kotlin {
androidNativeArm64("android64") {
binaries {
sharedLib { }
staticLib { }
}
}
}
下面來說一下使用動態(tài)庫的問題,對于通常的 C/C++ 庫來說,Kotlin 都可以簡單的通過 cinterop 來引入,官方也早就相關(guān)的文檔來說明(點擊查閱),同樣的,也可以在 C/C++ 里使用 Kotlin 的庫,官方文檔也說明了這一點(點擊查閱)。
在這里有一個問題,如官方文檔所述,在實際應用中為了一個函數(shù)去寫一大堆代碼顯然是不合適的,這里貼來官方的例子比較一下:
#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 的導出函數(shù):
@CName("Java_com_rarnu_sample_NativeAPI_sayHello")
fun sayHello(env: CPointer<JNIEnvVar>, thiz: jobject) { ... ... }
另外,全網(wǎng)搜索 Kotlin 調(diào)用自己的庫未果,而且經(jīng)過一系列常規(guī)的嘗試后發(fā)現(xiàn)兩個大家都遇到了的問題(第一個,第二個),顯然不可能往這些方向繼續(xù)進行。
其實 Kotlin 要使用自己的庫并沒有那么麻煩,只不過設計思路有些不同,因為有一個很神奇的中間層叫 klib,通過引用 klib 就可以
$ konanc sample.kt -p library -o sample
編譯庫文件源碼,可以得到一個 sample.klib 把它放到項目里,然后直接引用文件即可:
sourceSets {
... ...
macosMain {
dependencies {
implementation files('sample.klib')
}
}
}
這樣一來,這個 klib 就可以正確的代碼中引用到了。