1个不稳定版本
0.2.0 | 2022年8月8日 |
---|---|
0.1.0 |
|
#337 in 过程宏
115KB
2K SLoC
简介
此库在功能上类似于bindgen对C的支持,尽管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"
一旦添加,您需要为执行Jaffi创建一个build.rs
脚本,如下所示(请参阅集成测试以获取工作示例 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处理器中,并根据需要将错误转换为异常(反之亦然)。请参阅下面的Exceptions,Errors和Panics
。
特定类的包装器,即回调到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
将捕获恐慌并确保从原生方法返回一个适当的默认 null 值,这个值实际上毫无用处,因为异常应该使 Java 中的返回短路。
Java 类文件中的类型签名将用于评估异常。Rust 将生成一个枚举类型,其中包含可以在 Java 中抛出或由 jaffi 自动将 Rust 错误转换为 Java 异常的各种异常类型。Rust 生成的接口抽象出了方法接口中的这些对话。如果一个方法在其 throws
节中没有列出异常,但需要捕获这些异常,则可以通过生成的 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 时的生产力。
谢谢
该项目大量使用了这些crate,感谢所有为它们工作的人
cafebabe
- 一个 Java 类文件读取器jni
- Rust 中的最先进的 JNI 支持tinytemplate
- 用于所有 Rust 代码生成
谢谢!
许可证
根据您选择的以下任一项进行许可
- Apache License,版本 2.0,(LICENSE-APACHE 或 https://www.apache.org/licenses/LICENSE-2.0)
- MIT 许可证(LICENSE-MIT 或 https://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确说明,否则根据 Apache-2.0 许可证定义的,您有意提交给作品以供包含的贡献,将根据上述方式双许可,而无需任何额外的条款或条件。
依赖项
~1.6–3MB
~52K SLoC