6个版本

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

#221 in FFI

Download history 352/week @ 2024-03-14 302/week @ 2024-03-21 409/week @ 2024-03-28 352/week @ 2024-04-04 361/week @ 2024-04-11 408/week @ 2024-04-18 375/week @ 2024-04-25 336/week @ 2024-05-02 226/week @ 2024-05-09 257/week @ 2024-05-16 272/week @ 2024-05-23 370/week @ 2024-05-30 633/week @ 2024-06-06 573/week @ 2024-06-13 420/week @ 2024-06-20 399/week @ 2024-06-27

2,128次每月下载
robusta_jni中使用

MIT许可证

115KB
2.5K SLoC

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的镜像。

此示例仅获取应用程序授权的文件路径。

示例用法

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包提供的类型,然而为了与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 ArrayList
Box<[u8]> byte[]
jni::JObject<'env> (任何Java对象作为输入类型)
jni::jobject (任何Java对象作为输出)

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

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

限制

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

  • 仅通过不透明的 JObject/jobject 类型支持装箱类型
  • 自动类型转换仅限于上面概述的表格,尽管在需要时易于扩展。

贡献

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

依赖

~2.5MB
~50K SLoC