在 Ktor 中使用 JNI 库

Ktor 官方文档中,并没有讲述如何在 Ktor 内调用 JNI 库,然而在实际开发中,用到 JNI 的场景还是相当多的,特别是团队里有那么几个写 C++ 成瘾并且希望什么都走 JNI 的人的时候。

其实对于 JNI 的加载来说,问题永远只有一个,就是路径,把 JNI 放在哪里才能正常加载呢?

按以往在 Java 上搞 JNI 的经验来说,只要是 java.library.path 可以指向的路径均可,所以在 Ktor 下也可以如此进行,写一些简单的代码来看看这个路径在哪:

get("/path") {
    call.respondText { System.getProperty("java.library.path") }
}

然后在浏览器内请求 http://localhost:8080/path 即可看到效果,得到的路径为:

/Users/rarnu/Library/Java/Extensions
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
.

以上路径是在我自己的环境下得到的,你的环境或许会不同,不过无伤大雅。在路径列表的最后是一个点,即代表当前路径,换言之,将 JNI 库放在项目的根目录下是可行的。

那么事不宜迟,随手写个 JNI 库试试,为了简单起见,直接从 JNI 返回传入的字符串。

#include <jni.h>
extern "C" {
    JNIEXPORT jstring JNICALL Java_com_rarnu_sample_NativeSample_hello(JNIEnv* env, jclass obj, jstring text);
}
#include "Sample.h"
JNIEXPORT jstring JNICALL Java_com_rarnu_sample_NativeSample_hello(JNIEnv* env, jclass obj, jstring text) {
    return text;
}

然后就是编译,不得不说,C++ 挺麻烦的 :(

#!/bin/sh
JNIPATH=${JAVA_HOME}/include
JNIPLATFORM="linux"
PLATFORM=`uname -s`
SURFIX="so"
if [ "$PLATFORM" = "Darwin" ]; then
    JNIPLATFORM="darwin"
    SURFIX="dylib"
fi
echo $JNIPATH
echo $JNIPLATFORM
g++ main.cpp \
    -I${JNIPATH} \
    -I${JNIPATH}/${JNIPLATFORM} \
    -shared -fpic \
    -o libSample.${SURFIX} \
    -D JNI

好了,现在我们得到了一个 libSample.dylib,可以试着用一下了,此处需要注意的是,编译脚本兼容了 Mac 和 Linux,如果在 Linux 下执行,那么会得到 libSample.so,它们在使用上是一致的。

然后把这个 dylib 放到项目的根目录下,写一个调用的代码来使用它:

package com.rarnu.sample 
object NativeSample {
    init {
        System.loadLibrary("Sample")
    }
    external fun hello(text: String): String
}
get("/hello") {
    val txt = call.parameters["txt"] ?: ""
    call.respondText { NativeSample.hello(txt) }
}

完成后运行项目,就可以通过请求 http://localhost:8080/hello?txt=rarnu 来看到效果了。


下面来填个坑,我们在 IDE 里运行项目自然是没问题的,但是以 distribution 方式发布到线上后,却发现 libSample.dyliblibSample.so 丢失了,它并没有被打包到发布包内。要解决这一问题,我们必须修改 Gradle 脚本:

distTar {
    into("${project.name}-${project.version}") {
        from '.'
        include 'lib*.*'
    }
}
distZip {
    into("${project.name}-${project.version}") {
        from '.'
        include 'lib*.*'
    }
}

build.gradle 最后加上这些,然后再进行 gradle build 操作,就可以正常的把 JNI 库打进发布包了。

推荐阅读更多精彩内容