Kotlin替代javah的方法

19

javah自JDK 8起已经被弃用,并将在JDK 10中被移除/已被移除。根据JEP 313和弃用文本,应改用带有-h标志的javac:

警告:计划在下一个主要JDK版本中删除javah工具。该工具已被JDK 8中添加到javac的'-h'选项取代。建议用户迁移到使用javac'-h'选项;请参阅javac手册以获取更多信息。

问题是,javah是针对编译后的.class文件操作,而javac是针对源代码文件(即.java文件)操作。

javah与Kotlin和external函数配合使用良好,因为一切都编译成Java字节码,但由于在使用Kotlin时没有Java源代码文件,我看不到javac -h可以工作的任何方式。

是否有适用于Kotlin的javah替代方法或解决方法?


IntelliJ可以显示您的Kotlin源代码的字节码,然后还可以将该字节码反编译为Java源代码。那么您能否使用javah与该源代码呢?(有点笨拙,我知道...) - Greg Kopff
http://www.owsiak.org/how-to-solve-missing-javah-ugly-way/ - Oo.oO
5个回答

4

我编写了一个简单的Gradle任务用于生成JNI头文件,它与@Oo.oO发布的方法类似,但与Gradle更好地集成,因此可以在Windows和基于Unix的操作系统上运行。

val generateJniHeaders by tasks.creating {
    group = "build"
    dependsOn(tasks.getByName("compileKotlinJvm"))

    // For caching
    inputs.dir("src/jvmMain/kotlin")
    outputs.dir("src/jvmMain/generated/jni")

    doLast {
        val javaHome = 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/jvm/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 = 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 {
                    appendln("package $packageName;")
                    appendln("public class $className {")
                    for (method in nativeMethods) {
                        if ("()" in method) appendln(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++
                                }
                            }
                            appendln(updatedMethod)
                        }
                    }
                    appendln("}")
                }
                val outputFile = tmpDir.resolve(packageName.replace(".", "/")).apply { mkdirs() }.resolve("$className.java").apply { delete() }.apply { createNewFile() }
                outputFile.writeText(source)

                project.exec {
                    commandLine(javac, "-h", jniHeaderDirectory.absolutePath, outputFile.absolutePath)
                }.assertNormalExitValue()
            }
    }
}

4

我推荐使用gjavap


未来我还将实现一个更易于使用的命令行工具,提供类似于javap的功能。


标签为 0.2.0 的库在 jitpack 中构建失败了。而 0.1.1 没有任何主方法,因此基本上无法与 gradle 集成。请问是否有进一步更新的计划? - Animesh Sahu

3

目前没有内置的方法可以做到这一点。 Kotlin问题跟踪器上有一个开放问题,该问题在2019年11月提出,但截至目前还没有得到优先考虑并且没有目标版本 (链接)

使用JDK 10+生成头文件的唯一方法是使用javac -h,但它仅适用于Java源代码,而不适用于Kotlin。我在Oo.oO引用的“如何解决Java 10中缺失javah的问题——丑陋的方法”中测试了这种方法,暂时作为一种解决方法。步骤如下:

  1. 使用javap将字节码反编译成Java
  2. 编辑反编译后的Java以使其适合生成头文件(仅减少文件到仅包含native方法定义)。
  3. 通过javac -h运行反编译的Java代码以生成头文件

我正在考虑编写gradle脚本来完成此操作(或者希望其他人比我更快地完成!)。如果我能完成它,我会更新这篇文章。


1
你写了一个脚本/插件吗?我需要一个:p,最受欢迎的答案已经过时了,它的构建工件不正确,我不认为我现在会fork它... - Animesh Sahu
1
不好意思,我最终放弃了JNI并选择调用一个单独的进程。 :( - Adam Millerchip

2

在 Kotlin 开始生成适用于 JDK10 的字节码之前,您可以使用 JDK 9 或更低版本的 javah 工具处理编译后的 kotlin 类。

即使在那之后,您仍然可以将 jvmTarget=1.8 编译到 external 函数中并在生成的类上使用 javah


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接