Rust JNI工具库jni-utils的使用:简化Java原生接口交互与跨平台开发

Rust JNI工具库jni-utils的使用:简化Java原生接口交互与跨平台开发

简介

jni-utils是一个建立在jni crate之上的Rust库,它提供了更高级的概念来简化JNI交互。相比jni crate实现的JNI底层绑定,jni-utils更专注于常用的高级构造。

主要特性

  • 使用JFutureJStream类型实现与Java代码的异步调用
  • 常见Rust类型与其对应Java类型之间的转换
  • 使用try_block函数模拟try/catch

设计原则

jni-utils的核心设计原则是尽量减少Rust和Java代码之间的切换,同时使从Rust调用Java代码比从Java调用Rust代码更容易。调用Rust需要创建一个带有native方法的类并从Rust导出,而调用Java只需要使用JNIEnv::call_method()

使用方法

简单构建方式

$ cargo build --features=build-java-support

Java支持库JAR将放置在target/<config>/java/libs目录中。

高级构建方式

$ cargo build
$ cd java
$ ./gradlew build

添加依赖

build.gradle中添加:

dependencies {
    implementation 'io.github.gedgygedgy.rust:jni-utils:0.1.0'
}

完整示例

以下是一个使用jni-utils的完整示例:

use jni::JNIEnv;
use jni::objects::{JObject, JValue};
use jni_utils::init;

fn main() {
    // 初始化jni-utils
    init().unwrap();

    // 创建一个JVM实例
    let jvm = jni::JavaVM::new(jni::InitArgs::default()).unwrap();
    let env = jvm.attach_current_thread().unwrap();

    // 示例1: 调用Java方法
    let result = call_java_method(&env).unwrap();
    println!("Java方法返回结果: {}", result);

    // 示例2: 使用try_block处理异常
    match try_block_example(&env) {
        Ok(value) => println!("成功获取值: {}", value),
        Err(e) => println!("捕获到异常: {:?}", e),
    }
}

// 示例1: 调用Java方法
fn call_java_method(env: &JNIEnv) -> jni::errors::Result<i32> {
    // 获取System类
    let system_class = env.find_class("java/lang/System")?;
    
    // 调用currentTimeMillis方法
    let time = env.call_static_method(
        system_class,
        "currentTimeMillis",
        "()J",
        &[],
    )?.j()?;
    
    Ok(time as i32)
}

// 示例2: 使用try_block处理异常
fn try_block_example(env: &JNIEnv) -> jni::errors::Result<i32> {
    // 使用try_block捕获异常
    jni_utils::try_block(env, || {
        // 尝试执行可能抛出异常的代码
        let integer_class = env.find_class("java/lang/Integer")?;
        let number = env.new_object(
            integer_class,
            "(I)V",
            &[JValue::Int(42)],
        )?;
        
        // 调用parseInt方法,传入非数字字符串会抛出异常
        let parsed = env.call_static_method(
            "java/lang/Integer",
            "parseInt",
            "(Ljava/lang/String;)I",
            &[JValue::Object(&JObject::from("not_a_number"))],
        )?.i()?;
        
        Ok(parsed)
    })
}

注意事项

  • 某些jni-utils功能需要配套的Java支持库,因此在使用jni-utils前应调用jni_utils::init()
  • 与Tokio异步运行时配合使用时可能需要额外的类缓存处理

扩展完整示例

以下是一个更完整的jni-utils使用示例,展示了异步调用和类型转换功能:

use jni::{JNIEnv, JavaVM};
use jni::objects::{JObject, JString, JValue};
use jni_utils::{init, JFuture};
use std::future::Future;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 初始化jni-utils
    init().unwrap();

    // 创建JVM实例
    let jvm = JavaVM::new(jni::InitArgs::default())?;
    let env = jvm.attach_current_thread()?;

    // 示例3: 异步调用Java方法
    let future = call_async_java_method(&env)?;
    let result = future.await?;
    println!("异步调用结果: {}", result);

    // 示例4: 类型转换
    let converted = convert_types(&env)?;
    println!("转换后的值: {}", converted);

    Ok(())
}

// 示例3: 异步调用Java方法
fn call_async_java_method(env: &JNIEnv) -> jni::errors::Result<JFuture<i32>> {
    // 获取CompletableFuture类
    let future_class = env.find_class("java/util/concurrent/CompletableFuture")?;
    
    // 创建CompletableFuture实例
    let future = env.new_object(future_class, "()V", &[])?;
    
    // 调用complete方法
    env.call_method(
        future,
        "complete",
        "(I)Z",
        &[JValue::Int(42)],
    )?;
    
    // 将Java Future转换为Rust的JFuture
    JFuture::from_java(env, future)
}

// 示例4: 类型转换
fn convert_types(env: &JNIEnv) -> jni::errors::Result<String> {
    // 创建一个Java字符串
    let java_string = JString::from(env.new_string("Hello from Rust")?);
    
    // 转换为Rust字符串
    let rust_string: String = env.get_string(&java_string)?.into();
    
    Ok(rust_string)
}

1 回复

Rust JNI工具库jni-utils的使用:简化Java原生接口交互与跨平台开发

介绍

jni-utils是一个Rust库,旨在简化Rust与Java通过JNI(Java Native Interface)的交互过程。它提供了更符合Rust习惯的API,减少了直接使用JNI时的样板代码,使跨平台开发更加便捷。

主要特性

  • 简化JNI环境的获取和管理
  • 提供类型安全的Java对象和方法的访问
  • 自动处理异常转换
  • 支持Java和Rust之间的类型转换
  • 简化字符串处理

完整示例代码

下面是一个完整的demo,展示了如何使用jni-utils进行Rust与Java的交互:

// Cargo.toml 依赖
/*
[dependencies]
jni-utils = "0.1"
jni = "0.21"
tokio = { version = "1.0", features = ["full"] }
*/

use jni::JNIEnv;
use jni::objects::{JClass, JString, JObject};
use jni::sys::{jstring, jint};
use jni_utils::{JNIUtils, JMethodID, JFieldID};
use tokio::runtime::Runtime;

// 缓存的结构体
struct CalculatorCache {
    add_method: JMethodID,
    result_field: JFieldID,
}

// 基本示例:字符串处理
#[no_mangle]
pub extern "system" fn Java_com_example_Demo_greet(
    env: JNIEnv,
    _class: JClass,
    name: JString,
) -> jstring {
    let utils = JNIUtils::new(env);
    
    // Java字符串转Rust字符串
    let name_str: String = utils.get_string(&name).unwrap();
    
    // 创建新的Java字符串
    let response = format!("你好,{}!来自Rust的问候", name_str);
    utils.new_string(&response).unwrap().into_inner()
}

// 对象操作示例
#[no_mangle]
pub extern "system" fn Java_com_example_Demo_createUser(
    env: JNIEnv,
    _class: JClass,
    name: JString,
    age: jint,
) -> JObject {
    let utils = JNIUtils::new(env);
    
    // 创建Java对象
    let user = utils.new_object(
        "com/example/User",
        "(Ljava/lang/String;I)V",
        &[
            utils.get_string(&name).unwrap().into(), // 名字参数
            age.into()                              // 年龄参数
        ]
    ).unwrap();
    
    user
}

// 方法调用示例(带缓存)
#[no_mangle]
pub extern "system" fn Java_com_example_Demo_calculate(
    env: JNIEnv,
    _class: JClass,
    calculator: JObject,
    a: jint,
    b: jint,
) -> jint {
    let utils = JNIUtils::new(env);
    
    // 缓存方法和字段ID(实际应用中应该全局缓存)
    let cache = CalculatorCache {
        add_method: utils.get_method_id("com/example/Calculator", "add", "(II)I").unwrap(),
        result_field: utils.get_field_id("com/example/Calculator", "lastResult", "I").unwrap()
    };
    
    // 使用缓存的方法ID调用方法
    let result = utils.call_method_with_id(
        &calculator,
        &cache.add_method,
        &[a.into(), b.into()]
    ).unwrap();
    
    // 设置字段值
    utils.set_field_with_id(&calculator, &cache.result_field, result.i().unwrap()).unwrap();
    
    result.i().unwrap()
}

// 异步示例
#[no_mangle]
pub extern "system" fn Java_com_example_Demo_fetchAsyncData(
    env: JNIEnv,
    _class: JClass,
    callback: JObject,
) {
    let utils = JNIUtils::new(env);
    
    // 在单独线程中运行异步代码
    std::thread::spawn(move || {
        let rt = Runtime::new().unwrap();
        rt.block_on(async {
            // 模拟异步网络请求
            let data = fetch_remote_data().await;
            
            // 回调到Java
            utils.call_method(
                &callback,
                "onSuccess",
                "(Ljava/lang/String;)V",
                &[utils.new_string(&data).unwrap().into()]
            ).unwrap();
        });
    });
}

async fn fetch_remote_data() -> String {
    tokio::time::sleep(std::time::Duration::from_secs(2)).await;
    "异步数据加载完成".to_string()
}

// 异常处理示例
#[no_mangle]
pub extern "system" fn Java_com_example_Demo_validateInput(
    env: JNIEnv,
    _class: JClass,
    input: JString,
) {
    let utils = JNIUtils::new(env);
    let input_str: String = utils.get_string(&input).unwrap();
    
    if input_str.is_empty() {
        utils.throw(
            "java/lang/IllegalArgumentException", 
            "输入不能为空"
        ).expect("抛出异常失败");
    }
}

最佳实践

  1. 全局缓存:对于频繁使用的方法和字段ID,应该全局缓存而不是每次调用都查找
  2. 错误处理:所有JNI操作都应该检查返回值并处理错误
  3. 资源管理:注意及时释放本地引用,避免内存泄漏
  4. 线程安全:确保JNIEnv只在创建它的线程中使用
  5. 类型安全:充分利用jni-utils提供的类型安全接口

这个完整的demo展示了jni-utils的主要功能,包括字符串处理、对象创建、方法调用、异步操作和异常处理。实际使用时可以根据需要组合这些功能。

回到顶部