5 个不稳定版本

0.2.2 2023 年 10 月 30 日
0.2.1 2023 年 3 月 13 日
0.2.0 2021 年 8 月 7 日
0.1.0 2021 年 4 月 7 日
0.0.3 2020 年 9 月 21 日

#47 in FFI

Download history 355/week @ 2024-04-06 400/week @ 2024-04-13 411/week @ 2024-04-20 356/week @ 2024-04-27 227/week @ 2024-05-04 250/week @ 2024-05-11 245/week @ 2024-05-18 281/week @ 2024-05-25 450/week @ 2024-06-01 610/week @ 2024-06-08 536/week @ 2024-06-15 436/week @ 2024-06-22 650/week @ 2024-06-29 682/week @ 2024-07-06 218/week @ 2024-07-13 610/week @ 2024-07-20

2,242 每月下载量

MIT 许可证

88KB
910

包含 (JAR 文件, 59KB) gradle-wrapper.jar

robusta — Rust 和 Java 之间易用的互操作

Build Status Latest Version Docs

主分支文档

此库提供了一个过程宏,使得在 Rust 中编写 JNI 兼容的代码更加容易。

它可以自动转换 Rust 输入和输出类型(见 限制)。

[dependencies]
robusta_jni = "0.2"

使用方法

只需在正确的地方添加几个属性即可。

首先,在模块上使用 #[bridge] 属性,即可启用它通过 robusta 处理。

然后,我们还需要为每个将在 Rust 中实现的具有本地方法的类创建一个结构体,并且每个这样的结构体都必须使用带有对应 Java 包名称的 #[package] 属性进行注释。

之后,实现的函数可以写成普通的 Rust 函数,并且宏将负责将标记为公共的函数和具有 "jni" ABI 的函数转换为 Java 类型。默认情况下,如果转换失败,将抛出 Java 异常。

另一方面,如果您需要从 Rust 调用 Java 函数,您需要添加一个 "java" ABI 并在 self/&self/&mut self(或如果方法是静态的,则为第一个参数)之后添加一个 &JNIEnv 参数,并留空函数体。

在这些方法中,您可以附加一个 call_type 属性来管理如何处理转换和错误:默认情况下,隐含了 #[call_type(safe)],但您可以在任何时间切换到 #[call_type(unchecked)],通常不需要更改代码。

您还可以通过 #[input_type] 属性强制在输入参数上使用 Java 类型,这在例如 Android JNI 开发中可能很有用。

Android 特性

在 Android 应用中,要从 Rust 调用 Java 类,JVM 会使用调用栈来查找所需的类。但在 Rust 线程中,您不再有调用栈。
因此,为了能够调用 Java 类,您必须传递类引用而不是字符串类路径。

您可以在 robusta-android-example/src/thread_func.rs 中找到一个使用此功能的示例。

代码示例

您可以在 ./robusta-example 下找到一个示例。要运行它,您应该在您的 PATH 上有 javajavac,然后执行。

$ cd robusta-example
$ make java_run

# if you don't have `make` installed:
$ cargo build && javac com/example/robusta/HelloWorld.java && RUST_BACKTRACE=full java -Djava.library.path=../target/debug com.example.robusta.HelloWorld

Android 上的使用示例

您可以在 ./robusta-android-example 中找到一个 Robusta 用于 Android 的示例。要运行它,请使用 Android Studio 打开 robustaAndroidExample 项目。

Cargo 构建由 gradle 自动运行。

Rust 中的 lib.rs 是 Java 类 RobustaAndroidExample 的镜像。

此示例仅获取 App 授权路径的文件。

示例用法

Rust 方面

use robusta_jni::bridge;
use robusta_jni::convert::Signature;

#[bridge]
mod jni {
    #[derive(Signature)]
    #[package(com.example.robusta)]
    struct HelloWorld;

    impl HelloWorld {
        pub extern "jni" fn special(mut input1: Vec<i32>, input2: i32) -> Vec<String> {
            input1.push(input2);
            input1.iter().map(ToString::to_string).collect()
        }
    }
}

Java 方面

package com.example.robusta;

import java.util.*;

class HelloWorld {
    private static native ArrayList<String> special(ArrayList<Integer> input1, int input2);

    static {
        System.loadLibrary("robusta_example");
    }

    public static void main(String[] args) {
        ArrayList<String> output = HelloWorld.special(new ArrayList<Integer>(List.of(1, 2, 3)), 4);
        System.out.println(output)
    }
}

类型转换细节和扩展到自定义类型

有四个特质控制 Rust 类型如何转换为/从 Java 类型:(Try)FromJavaValue(Try)IntoJavaValue

这些特质分别用于输入和输出类型,实现它们是允许库执行自动类型转换所必需的。

这些特质利用了 jni crate 提供的类型,然而为了与 robusta 兼容性最佳,我们建议使用在 robusta_jni::jni 下重新导出的版本。

引发异常

您可以通过返回一个带有 jni::errors::ResultErr 变体来使 Rust 原生方法引发 Java 异常。

转换表

Rust Java
i32 int
bool boolean
char char
i8 byte
f32 float
f64 double
i64 long
i16 short
String String
Vec<T>† ArrayList<T>
Box<[u8]> byte[]
jni::JObject<'env> (任何 Java 对象作为输入类型)
jni::jobject (任何 Java 对象作为输出)

† 类型参数 T 必须实现适当的转换类型

‡ 特殊的 'env 生命周期 必须 使用

限制

目前转换机制存在一些限制

  • 仅通过不透明的 JObject/jobject 类型支持装箱类型
  • 自动类型转换仅限于上述表格中概述的内容,但如需扩展则很容易。

贡献

我很乐意接受外部贡献! :)

依赖项

~2.2–3.5MB
~62K SLoC