在 Rust 中嵌入 JVM
Source: Dev.to
请提供您希望翻译的具体文本内容,我将为您翻译成简体中文。
本文内容
- 在 Rust 应用程序中嵌入 JVM。
- 从 Rust 调用 Java 代码 以及 从 Java 调用 Rust 代码。
本文假设您已经熟悉 Java 和 Rust。完整的源代码可在本文的 GitHub 仓库中获取。
Source: …
Java 端 – 一个小型库
我们将使用一个小而刻意人工构造的示例来展示各种 Java‑Rust 交互特性。该库由三个类组成:
package me.ivanyu.java_library;
public class Calculator {
public static void add(int a, int b, ResultCallback callback) {
int sum = a + b;
String message = "Result: " + sum;
Result result = new Result(message);
callback.onResult(result);
}
}
@FunctionalInterface
public interface ResultCallback {
void onResult(Result result);
}
package me.ivanyu.java_library;
public class Result {
private final String message;
public Result(final String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
Calculator.add 计算两个整数的和,构造一条消息字符串,将其包装在 Result 对象中,最后调用提供的回调。
本地回调类
为了让 Rust 处理回调,我们在实现 ResultCallback 的类中声明一个 native 方法:
package me.ivanyu.java_library;
public class NativeCallback implements ResultCallback {
@Override
// `native` 关键字告诉 JVM 实现位于本地代码中。
public native void onResult(Result result);
}
构建 JAR
./gradlew clean jar
生成的 JAR 将位于 java/lib/build/libs/lib.jar。
Rust 端 – 依赖
将 jni crate 添加到你的 Cargo.toml 中。invocation 功能是从 Rust 启动 JVM 所必需的。
[dependencies]
jni = { version = "0.21.1", features = ["invocation"] }
在 Rust 中实现本地方法
// Prevent symbol mangling.
// https://doc.rust-lang.org/rustc/symbol-mangling/index.html
#[unsafe(no_mangle)]
// `extern "system"` ensures the correct calling convention for JNI.
// The name follows the convention described in
// https://docs.oracle.com/en/java/javase/21/docs/specs/jni/design.html#resolving-native-method-names
pub extern "system" fn Java_me_ivanyu_java_1library_NativeCallback_onResult(
mut env: JNIEnv,
_obj: JObject,
result_obj: JObject,
) {
// Call `Result.getMessage()` – signature: ()Ljava/lang/String;
let message_jstring = env
.call_method(result_obj, "getMessage", "()Ljava/lang/String;", &[])
.expect("Failed to call getMessage")
.l()
.expect("getMessage returned null");
// Convert the Java string to a Rust `String`.
let message_jstring = JString::from(message_jstring);
let message: String = env
.get_string(&message_jstring)
.expect("Couldn't get Java string")
.into();
println!("{}", message);
}
关键点
#[unsafe(no_mangle)]禁用名称混淆,以便 JVM 能定位该函数。extern "system"选择平台特定的 JNI 调用约定。- 方法名遵循 JNI 命名方案(
Java___)。 - 我们获取
Result.getMessage()返回的String并在 Rust 中打印。
从 Rust 启动 JVM 并调用 Java 代码
use jni::{
objects::{JObject, JString},
InitArgsBuilder, JNIEnv, JavaVM, NativeMethod, JNIVersion,
};
use std::path::Path;
fn main() {
// -------------------------------------------------------------------------
// 1️⃣ Initialise the JVM with the library JAR on the class path.
// -------------------------------------------------------------------------
let jar_path = Path::new("java/lib/build/libs/lib.jar");
let classpath_option = format!("-Djava.class.path={}", jar_path.display());
let jvm_args = InitArgsBuilder::new()
.version(JNIVersion::V8) // Java 8+ bytecode version
.option(&classpath_option) // Add our JAR to the class path
.build()
.expect("Failed to create JVM args");
let jvm = JavaVM::new(jvm_args).expect("Failed to create JVM");
// -------------------------------------------------------------------------
// 2️⃣ Attach the current thread to obtain a JNIEnv.
// -------------------------------------------------------------------------
let mut env = jvm
.attach_current_thread()
.expect("Failed to attach current thread");
// -------------------------------------------------------------------------
// 3️⃣ Register the native method for `NativeCallback`.
// -------------------------------------------------------------------------
let native_callback_class = env
.find_class("me/ivanyu/java_library/NativeCallback")
.expect("Failed to find NativeCallback class");
// Signature: (Lme/ivanyu/java_library/Result;)V
let native_methods = [NativeMethod {
name: jni::strings::JNIString::from("onResult"),
sig: jni::strings::JNIString::from("(Lme/ivanyu/java_library/Result;)V"),
fn_ptr: Java_me_ivanyu_java_1library_NativeCallback_onResult as *mut _,
}];
env.register_native_methods(native_callback_class, &native_methods)
.expect("Failed to register native method");
// -------------------------------------------------------------------------
// 4️⃣ Obtain the `Calculator` class and call its static `add` method.
// -------------------------------------------------------------------------
let calculator_class = env
.find_class("me/ivanyu/java_library/Calculator")
.expect("Failed to find Calculator class");
// Create an instance of `NativeCallback` (the Rust‑implemented callback).
let native_callback_obj = env
.new_object("me/ivanyu/java_library/NativeCallback", "()V", &[])
.expect("Failed to instantiate NativeCallback");
// Call: Calculator.add(int a, int b, ResultCallback callback)
env.call_static_method(
calculator_class,
"add",
"(IILme/ivanyu/java_library/ResultCallback;)V",
&[
jni::objects::JValue::from(2i32),
jni::objects::JValue::from(3i32),
jni::objects::JValue::from(native_callback_obj),
],
)
.expect("Failed to invoke Calculator.add");
}
程序的工作原理
- 创建 JVM,并将编译好的 JAR 加入类路径。
- 将当前操作系统线程附加到 JVM,获取
JNIEnv。 - 注册
NativeCallback.onResult的本地实现。 - 实例化
NativeCallback(其onResult方法在 Rust 中实现)。 - 调用
Calculator.add(2, 3, callback)。Java 代码计算和,构造Result,并调用本地回调,Rust 中会打印Result: 5。
Recap
- Embedding a JVM 在 Rust 二进制文件中非常简单,只需使用
jnicrate 的invocation功能即可。 - Native methods 可以通过遵循 JNI 命名约定 或 使用
RegisterNatives显式注册来链接。 - 本示例演示了完整的 Java → Rust → Java 循环:Java 调用 Rust 实现的回调,回调随后与 Java 对象交互。
欢迎克隆仓库,尝试不同的签名,或扩展示例以在两个运行时之间传递更复杂的数据结构。祝编码愉快!
Alternative native‑method registration snippet
::from("onResult"),
sig: jni::strings::JNIString::from("(Lme/ivanyu/java_library/Result;)V"),
fn_ptr: Java_me_ivanyu_java_1library_NativeCallback_onResult as *mut c_void,
}];
env.register_native_methods(&native_callback_class, &native_methods)
.expect("Failed to register native methods");
// Create an instance of NativeCallback
let callback_obj = env
// Our native callback code.
.alloc_object(&native_callback_class)
.expect("Failed to allocate NativeCallback object");
// Find the Calculator class and call the `add` method (static).
let calculator_class = env
.find_class("me/ivanyu/java_library/Calculator")
.expect("Failed to find Calculator class");
env.call_static_method(
&calculator_class,
"add",
"(IILme/ivanyu/java_library/ResultCallback;)V",
&[
jni::objects::JValue::Int(3),
jni::objects::JValue::Int(5),
jni::objects::JValue::Object(&callback_obj),
],
)
.expect("Failed to call add");
Running the program:
cargo run
Result: 8
进一步阅读
还有另一个类似的 crate,Duchess,它更高级,专注于易用性。它使用宏将 Java 类反射为 Rust 类型,并像调用本地 Rust 方法一样调用方法。这可能是另一篇文章的主题。