前言
Rust 语言是近几年来编程语言界的新秀之子,因其严格的内存安全保障机制而备受众多程序员的青睐与推崇。而 Rust 语言除了可用于编写独立运行的二进制程序以外,亦可用于编写动态链接库并被第三方程序动态加载调用。笔者趁 Rust 学习途中就动手借助 jni crate 从而利用 Rust 语言通过 JNI 实现 Java 程序中的本地方法,并将此练手项目以及其编写过程一字不落地记载于此。
前置:相关环境的设置与须要软件的安装
JDK
JDK 是编写 Java 程序须要的开辟组件。笔者利用的是 AdoptOpenJDK 版本 21 ,可在此处下载与你操作体系与架构相匹配的 AdoptOpenJDK 。详细安装过程受限于文章篇幅故略去,请自行检索安装方法。
笔者在此处贴出本身的 Java 版本信息以供读者对照:- $ /opt/adoptopenjdk-21.0.2+13/bin/java -version
- openjdk version "21.0.2" 2024-01-16 LTS
- OpenJDK Runtime Environment Temurin-21.0.2+13 (build 21.0.2+13-LTS)
- OpenJDK 64-Bit Server VM Temurin-21.0.2+13 (build 21.0.2+13-LTS, mixed mode, sharing)
复制代码 Rust
既然要编写 Rust 代码,首先需要设置好 Rust 开辟环境。笔者利用 Rustup 工具安装 Rust ,前往该链接可获取与你操作体系符合的安装工具或 shell 命令行。
笔者在此处贴出本身的 Rustup 版本信息以供读者对照:- $ rustup --version
- rustup 1.27.0 (bbb9276d2 2024-03-08)
- info: This is the version for the rustup toolchain manager, not the rustc compiler.
- info: The currently active `rustc` version is `rustc 1.76.0 (07dca489a 2024-02-04)`
复制代码 Gradle
由于笔者利用 Gradle 作为 Java 代码的构建工具,故需先安装 Gradle 。 Gradle 已在其官方文档中给出不同操作体系上的安装方法,不过是英文版。考虑到读者的阅读需要,故记载一下本身的安装方法(Linux 体系上)。
Gradle 官方保举采用 SDKMAN! 安装 Gradle ,笔者亦利用该方式来安装。前往该链接获取安装 shell 命令并实行(无需 sudo ,安装到用户主文件夹下):- $ curl -s "https://get.sdkman.io" | bash
复制代码 等待 SDKMAN! 安装完成后,重启电脑或重新打开 shell ,运行:稍等片刻, SDKMAN! 就自动安装好 Gradle 了。笔者在此处贴出本身的 Gradle 版本信息以供读者对照:- $ gradle --version
- ------------------------------------------------------------
- Gradle 8.7
- ------------------------------------------------------------
- Build time: 2024-03-22 15:52:46 UTC
- Revision: 650af14d7653aa949fce5e886e685efc9cf97c10
- Kotlin: 1.9.22
- Groovy: 3.0.17
- Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023
- JVM: 17.0.10 (Oracle Corporation 17.0.10+11-LTS-240)
- OS: Linux 6.8.2-zen2-1-zen amd64
复制代码 预备:Java 程序的编写
新起一个文件夹作为项目的根文件夹,笔者起名为 rust-jni-demo :- $ mkdir rust-jni-demo
- $ cd rust-jni-demo
复制代码 在项目根文件夹下另起一文件夹 java 作为 Java 代码部分的根目录:我们对此项目的 Java 代码采用 Gradle 构建工具。在该目录中初始化 Gradle 的相关配置:- $ gradle init --use-defaults --type java-application
复制代码 等待 Gradle 创建 Gradle Wrapper 与相关项目初始文件。完成后利用 Java IDE (笔者利用 IntelliJ IDEA )进入 app 目录并修改相关示例代码文件,编辑主类 App :- /*
- * This source file was generated by the Gradle 'init' task
- */
- package top.srcres.apps.rustjnidemo;
- import java.io.File;
- import java.nio.file.Path;
- public class App {
- static void loadRustLibrary() {
- System.out.println(System.getProperty("java.library.path"));
- System.loadLibrary("rust_jni_demo");
- }
- static native String hello(String input);
- public static void main(String[] args) {
- loadRustLibrary();
- String output = hello("string from Java");
- System.out.println(output);
- }
- }
复制代码 其中 hello 方法即为我们要在 Rust 代码中实现的 native 方法。切回 Gradle 项目根目录并运行 ./gradlew build 先构建一下项目。
接下来需要编辑 app 目录下的 build.gradle.kts 文件,为 Gradle 添加天生 JNI 头文件的 Task ,在文件末端加入:- val generateJniHeaders: Task by tasks.creating {
- val jniHeaderDir = file("src/main/generated/jni")
- group = "build"
- dependsOn(tasks.getByName("compileJava"))
- inputs.dir("src/main/java")
- outputs.dir(jniHeaderDir)
- 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/java/main")
- val tmpDir = file("build/tmp/jvmJni").apply { mkdirs() }
- val bodyExtractingRegex = """^.+\Rpublic \w* ?class ([^\s]+).*\{\R((?s:.+))\}\R$""".toRegex()
- val nativeMethodExtractingRegex = """.*\bnative\b.*""".toRegex()
- println("Beginning to generate JNI headers.")
- println("javaHome is ${javaHome.absolutePath}")
- println("javap is $javap")
- println("javac is $javac")
- 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)
- println("Generating for ${outputFile.absolutePath} into ${jniHeaderDir.absolutePath}")
- project.exec {
- commandLine(javac, "-h", jniHeaderDir.absolutePath, outputFile.absolutePath)
- }.assertNormalExitValue()
- }
- }
- }
复制代码 保存文件后切回 Gradle 项目根目录,运行刚才新添加的 Task , ./gradlew generateJniHeaders 。完成后可在 app/src/main/generated/jni 目录下找到天生的 JNI 头文件,应该能在其中看到如下内容:- /*
- * Class: top_srcres_apps_rustjnidemo_App
- * Method: hello
- * Signature: (Ljava/lang/String;)Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_top_srcres_apps_rustjnidemo_App_hello
- (JNIEnv *, jclass, jstring);
复制代码 自此 Java 代码部分编写完毕,接下来利用 Rust 代码实现这个预留的 native 方法。
实现:Rust 动态链接库的编写
切回到团体项目的根目录( java 的父目录),另起一目录用以存放 Rust 代码:- $ mkdir rust
- $ cargo new --name rust-jni-demo rust
复制代码 注意此处我们用 Rust 自带的包管理器 Cargo 创建了 rust 目录并自动地完成了相应的初始化工作,并将这个 Rust Crate 名称指定为 rust-jni-demo 。修改该目录中的 Cargo.toml 文件,添加依靠 cargo 并指定 Crate 范例为动态链接库:- [dependencies]
- jni = "0.21.1"
- [lib]
- crate-type = ["cdylib"]
复制代码 进入 src 目录,删掉默认的 main.rs 文件,新建 lib.rs 文件,在此文件中编写 native 方法的实现:- use jni::JNIEnv;
- use jni::objects::{JClass, JObject, JString, JValue};
- use jni::sys::{jint, jlong, jstring};
- use std::thread;
- use std::time::Duration;
- fn create_rust_string(src: &str) -> String {
- format!("Rust-created string, {}", src)
- }
- #[allow(non_snake_case)]
- #[no_mangle]
- pub extern "system" fn Java_top_srcres_apps_rustjnidemo_App_hello<'a>(
- mut env: JNIEnv<'a>,
- _: JClass<'a>,
- input: JString<'a>
- ) -> jstring {
- let input: String = env.get_string(&input).expect("Failed to get Java string.").into();
- let output = env.new_string(create_rust_string(&input)).expect("Failed to create Rust string.");
- output.into_raw()
- }
复制代码 回到 Rust Cargo 的根目录,构建 release 版本的 Cargo :构建完成后,在 target/release 目录下应能找到构建天生的动态链接库(笔者在 Linux 体系上构建天生 librust_jni_demo.so )。接下来需要让 Java 程序加载这个动态链接库从而调用其对于 native 方法的实现。
回到 Java 代码目录中,编辑 app/build.gradle.kts 文件,在 application 块中加入运行 Java 程序的 JVM 参数:- application {
- // Define the main class for the application.
- mainClass = "top.srcres.apps.rustjnidemo.App"
- applicationDefaultJvmArgs = listOf(
- "-Djava.library.path=../../rust/target/release/"
- )
- }
复制代码 运行 ./gradlew run , Java 程序出现以下输出,代表成功调用 Rust 代码所编写的 native 实现。- Rust-created string, string from Java
复制代码 后续:添加更多不同功能的 native 方法并实现
我们为 Java 程序主类 App 添加更多的静态 native 方法:- static int testInt;
- static String testString;
- static String testStringFromRust;
- static native String hello(String input);
- static native int helloInt(int input);
- static native int helloFromTestIntField();
- static native String helloFromTestStringField();
- static native void modifyTestStringFromRust(String input);
- static String callFromRust(String input) {
- System.out.println("Method callFromRust was invoked!");
- return "Java-side received: " + input;
- }
- static native String actCallFromRust(String input);
- static native void delayInRust(long timeMillis);
复制代码 并在 main 方法中予以调用:- public static void main(String[] args) {
- loadRustLibrary();
- String output = hello("string from Java");
- System.out.println(output);
- int outputInt = helloInt(114514);
- System.out.println(outputInt);
- testInt = 514;
- System.out.println(helloFromTestIntField());
- testString = "String static field from Java";
- System.out.println(helloFromTestStringField());
- modifyTestStringFromRust("string from Java #2");
- System.out.println(testStringFromRust);
- System.out.println("actCallFromRust result: " + actCallFromRust("string from Java #3"));
- System.out.println("Delay in Rust for 2 seconds...");
- delayInRust(2000);
- System.out.println("Delay done.");
- }
复制代码 转到 Rust 代码中实现这些 native 方法:- #[allow(non_snake_case)]
- #[no_mangle]
- pub extern "system" fn Java_top_srcres_apps_rustjnidemo_App_helloInt<'a>(
- _: JNIEnv<'a>,
- _: JClass<'a>,
- input: jint
- ) -> jint {
- input + 1919810
- }
- #[allow(non_snake_case)]
- #[no_mangle]
- pub extern "system" fn Java_top_srcres_apps_rustjnidemo_App_helloFromTestIntField<'a>(
- mut env: JNIEnv<'a>,
- class: JClass<'a>
- ) -> jint {
- let testInt = env.get_static_field(&class, "testInt", "I")
- .expect("Failed to get static field testInt")
- .i()
- .expect("Failed to convert testInt into jint");
- testInt + 114
- }
- #[allow(non_snake_case)]
- #[no_mangle]
- pub extern "system" fn Java_top_srcres_apps_rustjnidemo_App_helloFromTestStringField<'a>(
- mut env: JNIEnv<'a>,
- class: JClass<'a>
- ) -> jstring {
- let testStringObj = env.get_static_field(&class, "testString", "Ljava/lang/String;")
- .expect("Failed to get static field testString")
- .l()
- .expect("Failed to convert testString into JObject");
- let testString: String = env.get_string(&JString::from(testStringObj))
- .expect("Failed to get the value of testString")
- .into();
- let output = env.new_string(create_rust_string(&testString)).expect("Failed to create Rust string.");
- output.into_raw()
- }
- #[allow(non_snake_case)]
- #[no_mangle]
- pub extern "system" fn Java_top_srcres_apps_rustjnidemo_App_modifyTestStringFromRust<'a>(
- mut env: JNIEnv<'a>,
- class: JClass<'a>,
- input: JString<'a>
- ) {
- let inputStr: String = env.get_string(&input).expect("Failed to receive the argument: input").into();
- let testStringFromRust = env.new_string(create_rust_string(&inputStr)).expect("Failed to create Rust string.");
- let testStringFromRustObj = JObject::from(testStringFromRust);
- let testStringFromRustId = env.get_static_field_id(&class, "testStringFromRust", "Ljava/lang/String;")
- .expect("Failed to get the ID of static field testStringFromRust");
- env.set_static_field(&class, &testStringFromRustId, JValue::from(&testStringFromRustObj))
- .expect("Failed to set static field testStringFromRust");
- }
- #[allow(non_snake_case)]
- #[no_mangle]
- pub extern "system" fn Java_top_srcres_apps_rustjnidemo_App_actCallFromRust<'a>(
- mut env: JNIEnv<'a>,
- class: JClass<'a>,
- input: JString<'a>
- ) -> jstring {
- let inputStr: String = env.get_string(&input).expect("Failed to receive the argument: input").into();
- let testStringFromRust = env.new_string(create_rust_string(&inputStr)).expect("Failed to create Rust string.");
- let testStringFromRustObj = JObject::from(testStringFromRust);
- let callFromRustResult = env.call_static_method(&class, "callFromRust", "(Ljava/lang/String;)Ljava/lang/String;", &[JValue::from(&testStringFromRustObj)])
- .expect("Failed to invoke static method callFromRust");
- let callFromRustResultObj = callFromRustResult.l().expect("Failed to convert the method result into JObject.");
- JString::from(callFromRustResultObj).into_raw()
- }
- #[allow(non_snake_case)]
- #[no_mangle]
- pub extern "system" fn Java_top_srcres_apps_rustjnidemo_App_delayInRust<'a>(
- _: JNIEnv<'a>,
- _: JClass<'a>,
- input: jlong
- ) {
- let inputU = u64::try_from(input).expect("Attempting to call delayInRust with negative millisecond duration.");
- thread::sleep(Duration::from_millis(inputU));
- }
复制代码 先构建 Rust 动态链接库 cargo build --release ,再构建并运行 Java 程序 ./gradlew run 。将会得到如下输出:- ../../rust/target/release/
- Rust-created string, string from Java
- 2034324
- 628
- Rust-created string, String static field from Java
- Rust-created string, string from Java #2
- Method callFromRust was invoked!
- actCallFromRust result: Java-side received: Rust-created string, string from Java #3
- Delay in Rust for 2 seconds...
- Delay done.
复制代码 完备项目
完备的项目源码已上传至 GitHub 仓库 ,利用 MIT 协议开源。由于时间仓促没能就代码细节详细解释实现原理,读者可自行前往阅读研究。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |