1 个不稳定版本
0.2.0 | 2022年8月6日 |
---|
#1581 在 开发工具
78 每月下载量
用于 jaffi
35KB
556 行
简介
这个库的功能类似于 C 的 bindgen,但由于 Java 和 JNI 的调用约定不如其他语言简单,因此它生成了一些桥接函数,以便在 Rust 和 Java 之间进行集成。名字来源于 Ja(va)FFI -> Jaffi
警告 这还处于非常早期阶段,没有全面的测试套件来验证 FFI 边界上的每个功能是否正常。
构建此项目
需要安装 Rust 和 Java 工具链。
现在测试应该像预期那样工作(可能会有很多警告,此项目正在积极开发中),如果一切正常,构建详情后应该有此输出
$> just test
...
Running tests
loadLibrary succeeded for jaffi_integration_tests
running tests jaffi_integration_tests
void_1void: do nothing
void_1long__J: got 100
void_1long__JI: 100 + 10 = 110
void_1long__JI: 2147483647 + 2147483647 = 4294967294
add_1values_1native: calling java with: 2147483647, 2147483647
add_1values_1native: got result from java: 4294967294
print_1hello_1native_1static: calling print_hello, statically
hello!
print_1hello_1native: calling print_hello
hello!
call_1dad_1native with 732
All tests succeeded
入门指南
Jaffi 库将根据指定的配置参数扫描类文件。目前存在一些不足,目前只支持解压缩的类路径,即如果类路径中包含 jar 文件,则构建将失败。
要使用此库,它尚未发布到 Crates.io,您需要在 Cargo.toml 中添加类似以下的依赖项
[build-dependencies]
jaffi = "0.2.0"
[dependencies]
jaffi_support = "0.2.0"
添加后,您需要创建一个 build.rs
脚本来执行 Jaffi,如下所示(参见集成测试中的示例 build.rs)
fn main() -> Result<(), Box<dyn Error>> {
let class_path = class_path();
let classes = vec![Cow::from("net.bluejekyll.NativeClass")];
let classes_to_wrap = vec![Cow::from("net.bluejekyll.ParentClass")];
let output_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set"));
let jaffi = Jaffi::builder()
.native_classes(classes)
.classes_to_wrap(classes_to_wrap)
.classpath(vec![Cow::from(class_path)])
.output_dir(Some(Cow::from(output_dir)))
.build();
jaffi.generate()?;
Ok(())
}
如果Jaffi运行成功,它将在构建路径中生成一个名为 generated_jaffi.rs
的文件,位于 OUT_DIR
。这个Rust文件对接口的实现方式有一些期望。它寻找一个名为 super::{Class}RsImpl
的类型,即它期望这个类型在 super 模块中,即在包含生成代码的模块之上。可以将 generated_jaffi.rs
文件包含在一个模块中来实现这一点,请参见示例 NativeClassRsImpl
use crate::net_bluejekyll::{net_bluejekyll_NativeClass, net_bluejekyll_NativeClassClass};
mod net_bluejekyll {
include!(concat!(env!("OUT_DIR"), "/generated_jaffi.rs"));
}
impl<'j> net_bluejekyll::NativeClassRs<'j> for NativeClassRsImpl<'j> {
// implement methods here
}
该文件是从具有原生接口定义的Java类文件生成的,例如
public class NativeClass extends ParentClass {
// basic test
public static native void void_void();
}
使用生成的代码
生成文档
所有可用函数的发现都可以通过 cargo doc --document-private-items --open
轻松完成。
RsImpl实现了Rs特质
这是该库的主要优点之一。它将从Java生成类型安全的绑定,并要求*RsImpl
类型实现所有必需的原生函数。此外,它正确地将Rust调用和Java之间的类型进行转换(在撰写本文时,仅测试了基本类型)。编译器将有助于失败,直到所有原生接口都已实现。
从 integration_tests
的示例,这些Java原生接口
public class NativePrimitives extends ParentClass {
// basic test
public static native void voidVoid();
// a parameter
public static native void voidLong(long foo);
// ...
}
然后被生成到Rust中的一个特质
pub trait NativePrimitivesRs<'j> {
fn from_env(env: JNIEnv<'j>) -> Self;
fn void_void(&self, class: NetBluejekyllNativePrimitivesClass<'j>);
fn void_long_j(
&self,
class: NetBluejekyllNativePrimitivesClass<'j>,
arg0: i64
);
// ...
}
其中 env 应在 from_env
中捕获,它构建Rust类型。然后从关联的C FFI函数(无需直接使用这些函数)调用绑定
#[no_mangle]
pub extern "system" fn Java_net_bluejekyll_NativePrimitives_voidVoid<'j>(
env: JNIEnv<'j>,
class: NetBluejekyllNativePrimitivesClass<'j>
) -> JavaVoid {
// ...
}
#[no_mangle]
pub extern "system" fn Java_net_bluejekyll_NativePrimitives_voidLong__J<'j>(
env: JNIEnv<'j>,
class: NetBluejekyllNativePrimitivesClass<'j>,
arg0: JavaLong
) -> JavaVoid {
// ...
}
所有调用Rust的代码都正确地包装在panic处理程序中,并将错误转换为异常(反之亦然)
指定类的包装器,即回调到Java
在所有函数调用中,都提供了一个this
参数,可以调用类上的任何public
方法。对于静态方法,this
绑定到生成的类型 *Class
。静态方法调用和对象方法调用都共享一个特质,该特质为两者都实现了,暴露了所有public static
方法。
从 inegration_tests
的示例
/// A constructor method wrapped and then the type returned from Rust to Java
fn ctor(
&self,
_class: NetBluejekyllNativeStringsClass<'j>,
arg0: String,
) -> NetBluejekyllNativeStrings<'j> {
println!("ctor: {arg0}");
NetBluejekyllNativeStrings::new_1net_bluejekyll_native_strings_ljava_lang_string_2(
self.env, arg0,
)
}
支持超类
如果已在 build.rs
中指定为 classes_to_wrap
选项,则除了指定的超类之外,任何超类也会被包装,包括出现在参数中的任何类(以及它们在类路径中找到)都将生成包装器。要访问超类或接口及其方法,只需在对象上调用 this.as_{package}_{Class}()
,然后可以在对象上调用超类方法。
从 integration_tests
的示例
fn call_dad_native(
&self,
this: net_bluejekyll::NetBluejekyllNativePrimitives<'j>,
arg0: i32,
) -> i32 {
println!("call_dad_native with {arg0}");
let parent = this.as_net_bluejekyll_parent_class();
parent.call_1dad(self.env, arg0)
}
异常、错误和恐慌
Rust 代码中的任何恐慌将通过 std::panic::set_hook
和 std::panic::catch_unwind
被捕获。恐慌钩子将在 Java 中创建一个 RuntimeException
(基于 Rust 中的 PanicInfo
)。catch_unwind
将捕获恐慌并确保从本地方法返回一个适当的默认空值,这个值基本上是无用的,因为异常应该短路 Java 中的返回。
Java 类文件中的类型签名将用于异常。Rust 将生成一个枚举类型,包含可以在 Java 中抛出或由 jaffi 自动将 Rust 错误转换为 Java 异常的各种异常类型。Rust 中生成的接口将这些对话从方法接口中抽象出来。如果一个方法在其 throws
部分中没有列出异常,但需要捕获这些异常,可以通过生成的 JNIEnv
手动完成,这些 JNIEnv
对生成的方 法是可用的。
来自 integration_tests
的示例
fn throws_something(
&self,
_this: NetBluejekyllExceptions<'j>,
) -> Result<(), Error<SomethingExceptionErr>> {
Err(Error::new(
SomethingExceptionErr::SomethingException(SomethingException),
"Test Message",
))
}
fn catches_something(
&self,
this: net_bluejekyll::NetBluejekyllExceptions<'j>,
) -> net_bluejekyll::NetBluejekyllSomethingException<'j> {
let ex = this
.i_always_throw(self.env)
.expect_err("error expected here");
#[allow(irrefutable_let_patterns)]
if let SomethingExceptionErr::SomethingException(SomethingException) = ex.throwable() {
net_bluejekyll::NetBluejekyllSomethingException::from(JObject::from(ex.exception()))
} else {
panic!("expected SomethingException")
}
}
/// this panic will generate an RuntimeException in Java.
fn panics_are_runtime_exceptions(&self, _this: NetBluejekyllExceptions<'j>) {
panic!("{}", "Panics are safe".to_string());
}
接下来是什么?
我构建这个是为了帮助我正在做的另一个项目,我在那里不断地追踪 FFI 绑定中的错误,因为变量改变了,签名没有得到适当的更新。这应该有助于减少这些简单的错误,并在使用 JNI 和 Rust 的工作中提高生产力。
谢谢
这个项目大量使用了这些 crates,感谢为它们工作过的每个人
cafebabe
- 一个 Java 类文件读取器jni
- Rust 中的最先进的 JNI 支持tinytemplate
- 用于所有 Rust 代码生成
谢谢!
许可
根据您的选择,许可为以下之一
- Apache 许可证 2.0 版,(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证 (LICENSE-MIT 或 https://opensource.org/licenses/MIT)
。
贡献
除非您明确说明,否则任何旨在包含在作品中的贡献(根据 Apache-2.0 许可证定义),都应按上述方式双许可,不附加任何额外条款或条件。
依赖关系
~1.3–2.6MB
~44K SLoC