#jni #java #call

jni-simple

简单Rust包装器,用于JNI (Java Native Interface) API

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

Download history 309/week @ 2024-06-26 224/week @ 2024-07-03 65/week @ 2024-07-10 133/week @ 2024-07-17 115/week @ 2024-07-24 14/week @ 2024-07-31 1/week @ 2024-08-07 99/week @ 2024-08-14

241 每月下载量

MIT/Apache

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.loadSystem.loadLibrary加载的库时,这就不重要了。

功能

loadjvm

本功能提供使用 libloading 包动态链接 jvm 的功能,该包包含指向 libjvm.sojvm.dll 的绝对路径的字符串。

注意:如果您不想使用 libloading 包但仍然需要启动 JVM,则提供了从 JNI_CreateJavaVM 函数的指针加载 JVM 的方法,而不是从 dll/so 文件加载。如果您想自己使用 dlopenLoadLibraryA 等方式来进行动态链接,请这样做。

注意:在编写由 System.loadSystem.loadLibrary 加载的库时,不应使用此功能。这只会添加不必要的依赖。

断言

此功能启用代码中的断言。这对于调试和测试非常有用。如上所述,这些检查会导致性能大幅下降,不应在生产代码中使用。此功能不应与 -Xcheck:jni 一起使用,因为这些检查包含对 env.ExceptionCheck() 的调用,这会使 JVM 误以为您的用户代码检查异常,而它可能并未这样做。

建议在使用 -Xcheck:jni 测试您的代码之前或之后使用此功能,具体取决于您要解决的问题。断言通常在检测空指针或无效参数等方面比 JVM 检查做得更好,而 JVM 检查能够更好地捕获缺少异常检查或 JVM 本地栈溢出。

依赖项

~0–5MB