Вызов ржавчины с Java
Я использую Rust 1.0 beta и смог создать небольшой пример для вызова функций, написанных на Rust с Java. Я просто скомпилировал следующий код Rust в mylib.rs, используя rustc, который создает mylib.dll в Windows:
#![crate_type = "dylib"]
use std::any::Any;
#[no_mangle]
pub extern fn Java_tests_Test_hello(env: *const Any, jclass: *const Any) {
println!("hello from rust");
}
#[no_mangle]
pub extern fn Java_tests_Test_sum(env: *const Any, jclass: *const Any, a: i32, b: i32) -> i32 {
return a + b;
}
Затем я могу вызывать эти функции из тестов класса Java. Тест:
package tests;
import java.io.File;
public class Test {
public static native void hello();
public static native int sum(int a, int b);
public static void main(String[] args) {
File f = new File("mylib.dll");
System.load(f.getAbsolutePath());
Test.hello();
System.out.println(Test.sum(20, 22));
}
}
Запуск главной страницы Java печатает ожидаемый результат:
hello from rust
42
В методах Rust я объявил env
как указатель на тип Any
, но на самом деле это C-структура с указателями на функции, как описано в (пример кода 4-1), которые необходимы для обмена данными со средой выполнения Java.
Из этого ответа я понял, что такие структуры с указателями функций должны иметь аналог в коде Rust для вызова этих функций. Поэтому я попытался реализовать такую структуру, установив все значения полей в *mut Any
, за исключением поля GetVersion
:
#[repr(C)]
pub struct JavaEnv {
reserved0: *mut Any,
reserved1: *mut Any,
reserved2: *mut Any,
reserved3: *mut Any,
GetVersion: extern "C" fn(env: *mut JavaEnv) -> i32,
DefineClass: *mut Any,
FindClass: *mut Any,
…
Когда я вызываю следующую функцию из Java, которая должна вызывать функцию GetVersion, JVM аварийно завершает работу:
#[no_mangle]
pub extern fn Java_tests_Test_helloJre(jre: *mut JavaEnv, class: *const Any) {
unsafe {
let v = ((*jre).GetVersion)(jre);
println!("version: {:?}", v);
}
}
Как я могу правильно вызвать функцию GetVersion? Обратите внимание, что я действительно новичок в подобных материалах, поэтому, пожалуйста, не стесняйтесь редактировать этот вопрос.
Ответы
Ответ 1
Помимо проблемы, заключающейся в том, что *mut Any
/*const Any
являются указателями жира, существует также факт, что собственные функции JNI используют двойное косвенное обращение при доступе к структуре JNINativeInterface
:
struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;
jint (JNICALL *GetVersion)(JNIEnv *env);
Здесь вы можете видеть, что JNIEnv
является указателем на структуру JNINativeInterface_
, которая фактически содержит поля, которые вы представили, и GetVersion
принимает указатель на JNIEnv
- то есть для этого требуется указатель на указатель до JNINativeInterface_
. Эта программа Rust работает на моей машине (используется Rust ночной, но тот же код будет работать в бета-версии с внешним ящиком libc):
#![crate_type="dylib"]
#![feature(libc)]
extern crate libc;
use libc::c_void;
#[repr(C)]
pub struct JNINativeInterface {
reserved0: *mut c_void,
reserved1: *mut c_void,
reserved2: *mut c_void,
reserved3: *mut c_void,
GetVersion: extern fn(env: *mut JNIEnv) -> i32,
_opaque_data: [u8; 1824]
}
pub type JNIEnv = *const JNINativeInterface;
#[no_mangle]
pub extern fn Java_tests_Test_helloJre(jre: *mut JNIEnv, class: *const c_void) {
println!("Invoked native method, jre: {:p}, class: {:p}", jre, class);
unsafe {
let v = ((**jre).GetVersion)(jre);
println!("version: {:?}", v);
}
}
Java-копия:
package tests;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Test {
public static native void helloJre();
public static void main(String[] args) {
Path p = Paths.get("libtest.dylib");
System.load(p.toAbsolutePath().toString());
Test.helloJre();
}
}
Призвание:
% javac tests/Test.java
% java tests.Test
Invoked native method, jre: 0x7f81240011e0, class: 0x10d9808d8
version: 65544
65544 - 0x10008, и действительно, я запускаю это под Oracle JVM 1.8.
Я думаю, вы можете опустить поле _opaque_data
, поскольку структура JNINativeInterface
всегда передается указателем, поэтому, если вам нужно только несколько первых полей из структуры, вы можете объявить только их и игнорировать остальные.
Ответ 2
Более простой подход заключается в использовании JnrFFI. Проект JRuby сильно использует JnrFFI, и он, вероятно, станет основой для нового Java FFI JEP. Это в основном исключает возможность написания всей взлома JNI. Вот пример кода, который использует JnrFFI для вызова функции Rust из Java:
Код Java
public static interface RustLib {
int double_input(int i);
}
public static String getLibraryPath(String dylib) {
File f = new File(JavaRustFFI.class.getClassLoader().getResource(mapLibraryName(dylib)).getFile());
return f.getParent();
}
public static void main(String[] args) {
String dylib = "double_input";
System.setProperty("jnr.ffi.library.path", getLibraryPath(dylib));
RustLib rlib = LibraryLoader.create(RustLib.class).load(dylib);
int r = rlib.double_input(20);
System.out.println("Result from rust double_input: " + r);
}
Код ржавчины
#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
input * 2
}
Вот полный код