0%

在kotlin multiplatform里使用jni

折腾了小半年flutter,感觉还是kotlin写的舒服,听说jb公司的compose能在桌面端跑了,就下了demo试了下,无奈桌面端不能像android一样编译jni库。百度了半天,只有这个教程能用,但代码并不全,要改改才能用。

桌面端

首先整个cmd编译C++,在gradle里运行shell命令也太麻烦了:

1
2
3
4
5
6
@echo off

call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64
set BUILD_DIR=./.cxx
cmake -S %~dp0/../cxx -B %BUILD_DIR%
cmake --build %BUILD_DIR% --config Release

接下来在gradle里整个task调用这个cmd:

1
2
3
4
5
6
7
8
9
10
tasks.create<Exec>("buildJniNativeWindows") {
group = "build"

inputs.dir(rootDir.resolve("cxx"))
outputs.dir(projectDir.resolve(".cxx/Release"))

workingDir(projectDir)
executable = "cmd"
args("/C", "build-windows.cmd")
}

然后在jvm编译的时候调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
kotlin {
jvm {
withJava()

val processResources = compilations["main"].processResourcesTaskName
(tasks[processResources] as ProcessResources).apply {
onlyIf { currentOs.isWindows }
dependsOn("buildJniNativeWindows")
from(projectDir.resolve(".cxx/Release"))
}
}
...
}

压根没看懂。调了半天算是猜到了.cxx/Release是C++的dll生成的位置,gradle会把这个文件夹的文件全拷贝到资源文件里面。

但是资源文件里的dll并不能被System.loadLibrary()调用,所以还得在桌面端实现从资源文件加载dll的代码:

1
2
3
4
5
6
7
8
9
10
actual fun jniLoadLibrary(name: String) {
val fname = name + ".dll"
val ins = object {}::class.java.getResourceAsStream(fname)
val file = java.io.File(System.getProperty("java.io.tmpdir") + "/" + fname)
val fos = file.outputStream()
ins.copyTo(fos)
ins.close()
fos.close()
System.load(file.toString())
}

而对应的android端代码就直接:

1
2
3
actual fun jniLoadLibrary(name: String) {
System.loadLibrary(name)
}

安卓端

安卓端就简单了,直接在gradle里加:

1
2
3
4
5
6
7
8
android {
...
externalNativeBuild {
cmake {
setPath("$rootDir/cxx/CMakeLists.txt")
}
}
}