Table of Contents
问题
最近在用kotlin写jni,但是生成头文件的时候遇到了些问题。 首先 javah 在java >= 1.9 就被取消用javac -h代替,但是javac对kotlin不适用,kotlinc也没有 -h 这个生成头文件的选项。
解决方法
在stackoverflow论坛找到了个解决方案,那个人提供了一个gradle task,大概原理是先用complieKotlin任务(或kotlinc)生成class字节码,再用javac编译回java文件,然后再调用javac -h 针对那个java文件生成jni头文件,我稍微修复了下,然后迁移到gradle kotlin dsl(就kts脚本),代码在下面。
使用方法
复制到build.gradle.kts的最外层就可以了,然后sync一下gradle,然后在gradle task里的build分类下就有generate jniheader这个任务了,StackOverflow原贴地址https://stackoverflow.com/a/65661275/14646226
需要改下代码里的inputs.dir("src/main/kotlin")
到你的kt源码文件夹, outputs.dir("src/main/generated/jni")
到你想的输出文件夹
代码
val generateJniHeaders: Task by tasks.creating { group = "build" dependsOn(tasks.getByName("compileKotlin"))
// For caching inputs.dir("src/main/kotlin") outputs.dir("src/main/generated/jni")
doLast { val javaHome = org.gradle.internal.jvm.Jvm.current().javaHome val javap = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javap") }?.absolutePath ?: error("javap not found") val javac = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javac") }?.absolutePath ?: error("javac not found") val buildDir = file("build/classes/kotlin/main") val tmpDir = file("build/tmp/jvmJni").apply { mkdirs() }
val bodyExtractingRegex = """^.+\Rpublic \w* ?class ([^\s]+).*\{\R((?s:.+))\}\R$""".toRegex() val nativeMethodExtractingRegex = """.*\bnative\b.*""".toRegex()
buildDir.walkTopDown() .filter { "META" !in it.absolutePath } .forEach { file -> if (!file.isFile) return@forEach
val output = com.gradle.publish.plugin.dep.org.apache.commons.io.output.ByteArrayOutputStream().use { project.exec { commandLine(javap, "-private", "-cp", buildDir.absolutePath, file.absolutePath) standardOutput = it }.assertNormalExitValue() it.toString() }
val (qualifiedName, methodInfo) = bodyExtractingRegex.find(output)?.destructured ?: return@forEach
val lastDot = qualifiedName.lastIndexOf('.') val packageName = qualifiedName.substring(0, lastDot) val className = qualifiedName.substring(lastDot+1, qualifiedName.length)
val nativeMethods = nativeMethodExtractingRegex.findAll(methodInfo).mapNotNull { it.groups }.flatMap { it.asSequence().mapNotNull { group -> group?.value } }.toList() if (nativeMethods.isEmpty()) return@forEach
val source = buildString { appendLine("package $packageName;") appendLine("public class $className {") for (method in nativeMethods) { if ("()" in method) appendLine(method) else { val updatedMethod = StringBuilder(method).apply { var count = 0 var i = 0 while (i < length) { if (this[i] == ',' || this[i] == ')') insert(i, " arg${count++}".also { i += it.length + 1 }) else i++ } } appendLine(updatedMethod) } } appendLine("}") } val outputFile = tmpDir.resolve(packageName.replace(".", "/")).apply { mkdirs() }.resolve("$className.java").apply { delete() }.apply { createNewFile() } outputFile.writeText(source)
project.exec { commandLine(javac, "-h", "src/main/generated/jni", outputFile.absolutePath) }.assertNormalExitValue() } }}
gradle task位置截图
