5个版本
新版本 0.1.4 | 2024年8月19日 |
---|---|
0.1.3 | 2024年7月21日 |
0.1.2 | 2024年7月4日 |
0.1.1 | 2024年7月1日 |
0.1.0 | 2024年6月30日 |
#69 in FFI
241 每月下载量
120KB
2K SLoC
jni-simple
这个crate包含了一个简单的手写的Rust包装器,用于JNI (Java Native Interface) API。它对JNI调用没有任何魔法,允许您像在C中一样使用它。
示例
从共享对象文件或dll加载JVM
注意:此示例假设已启用loadjvm功能!
use jni_simple::{*};
use std::ptr::null;
#[test]
fn test() {
unsafe {
// On linux/unix:
jni_simple::load_jvm_from_library("/usr/lib/jvm/java-11-openjdk-amd64/lib/server/libjvm.so")
.expect("failed to load jvm");
// On windows:
// jni_simple::load_jvm_from_library("C:\\Program Files\\Java\\jdk-17.0.1\\jre\\bin\\server\\jvm.dll")
// .expect("failed to load jvm");
//Adjust JVM version and arguments here, args are just like the args you pass on the command line.
//You could provide your classpath here for example or configure the jvm heap size.
//Default arguments (none) will do for this example.
let args : Vec<String> = vec![];
let (_jvm, env) = JNI_CreateJavaVM_with_string_args(JNI_VERSION_1_8, &args).expect("failed to create jvm");
//This code does not check for failure or exceptions checks or "checks" for success in general.
let sys = env.FindClass_str("java/lang/System");
let nano_time = env.GetStaticMethodID_str(sys, "nanoTime", "()J");
let nanos = env.CallStaticLongMethodA(sys, nano_time, null());
//Calls System.nanoTime() and prints the result
println!("{}", nanos);
}
}
编写实现本地方法的JNI共享库
此示例的完整版本可以在仓库中的example_project文件夹中找到。
#![allow(non_snake_case)]
use std::io::Write;
use jni_simple::{*};
use std::os::raw::{c_void};
use std::ptr::{null, null_mut};
use std::thread;
use std::time::Duration;
use std::io::stdout;
//Optional: Only needed if you need to spawn "rust" threads that need to interact with the JVM.
extern "system" {
fn JNI_CreateJavaVM(invoker: *mut c_void, env: *mut c_void, initargs: *mut c_void) -> jint;
fn JNI_GetCreatedJavaVMs(array: *mut c_void, len: jsize, out: *mut jsize) -> jint;
}
#[no_mangle]
pub unsafe extern "system" fn JNI_OnLoad(vm: JavaVM, _reserved: *mut c_void) -> jint {
//Optional: Only needed if you need to spawn "rust" threads that need to interact with the JVM.
jni_simple::init_dynamic_link(JNI_CreateJavaVM as *mut c_void, JNI_GetCreatedJavaVMs as *mut c_void);
//All error codes are jint, never JNI_OK. See JNI documentation for their meaning when you handle them.
//This is a Result<JNIEnv, jint>.
let env : JNIEnv = vm.GetEnv(JNI_VERSION_1_8).unwrap();
//This code does not check for failure or exceptions checks or "checks" for success in general.
let sys = env.FindClass_str("java/lang/System");
let nano_time = env.GetStaticMethodID_str(sys, "nanoTime", "()J");
let nanos = env.CallStaticLongMethodA(sys, nano_time, null());
println!("RUST: JNI_OnLoad {}", nanos);
stdout().flush().unwrap();
return JNI_VERSION_1_8;
}
//Would be called from java. the signature in java is org.example.JNITest#test()
#[no_mangle]
pub unsafe extern "system" fn Java_org_example_JNITest_test(env: JNIEnv, _class: jclass) {
//This code does not check for failure or exceptions checks or "checks" for success in general.
let sys = env.FindClass_str("java/lang/System");
let nano_time = env.GetStaticMethodID_str(sys, "nanoTime", "()J");
let nanos = env.CallStaticLongMethodA(sys, nano_time, null());
println!("RUST: Java_org_example_JNITest_test {}", nanos);
stdout().flush().unwrap();
thread::spawn(|| {
thread::sleep(Duration::from_millis(2000));
//This can be done anywhere in the application at any time.
let vms : JavaVM = jni_simple::JNI_GetCreatedJavaVMs().unwrap() // error code is once again a jint.
.first().unwrap().clone(); //There can only be one JavaVM per process as per oracle spec.
//You could also provide a thread name or thread group here.
let mut n = JavaVMAttachArgs::new(JNI_VERSION_1_8, null(), null_mut());
vms.AttachCurrentThread(&mut n).unwrap();
let env = vms.GetEnv(JNI_VERSION_1_8).unwrap();
let sys = env.FindClass_str("java/lang/System");
let nano_time = env.GetStaticMethodID_str(sys, "nanoTime", "()J");
let nanos = env.CallStaticLongMethodA(sys, nano_time, null());
println!("RUST thread delayed: Java_org_example_JNITest_test {}", nanos);
stdout().flush().unwrap();
vms.DetachCurrentThread();
});
}
此crate的主要目标
不要假装JNI是“安全的”
JNI本身是不安全的(从Rust的角度看),任何试图强制执行安全性的尝试都可能导致性能或API复杂性问题。此crate提供的所有JNI方法都被标记为“不安全”,正如它们应该的那样。
保持JNI的简单类型系统不变
所有类型,如jobject、jstring、jarray等,在C中表示为指针的不可见句柄,在Rust中也被表示为原始不可见指针,彼此之间是类型别名。这本质上只是向用户提供了提示,并不强制执行任何类型安全性,因为这有时会阻碍与JNI的工作。
设计用于JVM的运行时动态链接
问题:现有依赖于jni-sys crate的jni crate需要动态链接器可以解析JVM。有两种方法可以实现。第一种是将JVM静态链接到二进制文件中,这很少做,非常繁琐,文档也不全。第二种是为链接器提供JVM,以便ldd可以找到它,但我从未在现实中看到过这种情况。
这个crate是为更常见的用例开发的,即JVM在系统的某个地方可用,并留给crate的用户编写必要的代码来查找和加载JVM。
这允许在编写启动应用程序时具有最大的灵活性,例如,可能首先从互联网上下载JVM。很明显,当编写不启动JVM的本地库时,因为它是一个实现某些本地方法并由System.load
或System.loadLibrary
加载的库时,这就不重要了。
功能
loadjvm
本功能提供使用 libloading
包动态链接 jvm 的功能,该包包含指向 libjvm.so
或 jvm.dll
的绝对路径的字符串。
注意:如果您不想使用 libloading
包但仍然需要启动 JVM,则提供了从 JNI_CreateJavaVM 函数的指针加载 JVM 的方法,而不是从 dll/so 文件加载。如果您想自己使用 dlopen
或 LoadLibraryA
等方式来进行动态链接,请这样做。
注意:在编写由 System.load
或 System.loadLibrary
加载的库时,不应使用此功能。这只会添加不必要的依赖。
断言
此功能启用代码中的断言。这对于调试和测试非常有用。如上所述,这些检查会导致性能大幅下降,不应在生产代码中使用。此功能不应与 -Xcheck:jni
一起使用,因为这些检查包含对 env.ExceptionCheck()
的调用,这会使 JVM 误以为您的用户代码检查异常,而它可能并未这样做。
建议在使用 -Xcheck:jni
测试您的代码之前或之后使用此功能,具体取决于您要解决的问题。断言通常在检测空指针或无效参数等方面比 JVM 检查做得更好,而 JVM 检查能够更好地捕获缺少异常检查或 JVM 本地栈溢出。
依赖项
~0–5MB