Llamando Rust desde Java
jni (2)
Aparte del problema de que *mut Any
/ *const Any
son punteros geniales, también existe el hecho de que las funciones JNI nativas utilizan doble JNINativeInterface
indirecto al acceder a la estructura de JNINativeInterface
:
struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;
jint (JNICALL *GetVersion)(JNIEnv *env);
Aquí, puede ver que JNIEnv
es un puntero a la estructura JNINativeInterface_
que en realidad contiene los campos que presentó, y GetVersion
acepta un puntero a JNIEnv
, es decir, requiere un puntero a un puntero a JNINativeInterface_
. Este programa Rust funciona en mi máquina (se usa Rust nightly pero el mismo código funcionaría en beta con una caja externa de 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);
}
}
Contraparte de 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();
}
}
Invocación:
% javac tests/Test.java
% java tests.Test
Invoked native method, jre: 0x7f81240011e0, class: 0x10d9808d8
version: 65544
65544 es 0x10008, y de hecho, estoy ejecutando esto bajo Oracle JVM 1.8.
Supongo que puede omitir el campo _opaque_data
ya que la estructura JNINativeInterface
siempre se pasa por un puntero, por lo que si solo necesita varios primeros campos de la estructura, puede declararlos solo e ignorar el resto.
Estoy usando Rust 1.0 beta y pude crear un pequeño ejemplo para llamar a funciones escritas en Rust desde Java. Simplemente compilé el siguiente código Rust en mylib.rs usando rustc que produce un mylib.dll en 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;
}
Entonces puedo llamar a estas funciones desde una prueba de clase de Java. Prueba:
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));
}
}
Ejecutando el Java principal imprime el resultado esperado:
hello from rust
42
En los métodos de Rust, declaré env
como un puntero al tipo Any
pero en realidad es una estructura C con punteros a las funciones como se describe en la documentación (Ejemplo de código 4-1) que se requieren para intercambiar datos con el tiempo de ejecución de Java.
De esta answer , entendí que tales estructuras con punteros de función deberían tener una contraparte en el código Rust para llamar a estas funciones. Así que intenté implementar una estructura de este tipo estableciendo todos los valores de campo en *mut Any
excepto en el campo 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,
…
Cuando llamo a la siguiente función desde Java, que debería llamar a la función GetVersion, la JVM se bloquea:
#[no_mangle]
pub extern fn Java_tests_Test_helloJre(jre: *mut JavaEnv, class: *const Any) {
unsafe {
let v = ((*jre).GetVersion)(jre);
println!("version: {:?}", v);
}
}
¿Cómo debo llamar a la función GetVersion correctamente? Tenga en cuenta que soy realmente nuevo en este tipo de cosas, así que no dude en editar esta pregunta si es necesario.
Un enfoque más simple sería utilizar JnrFFI . El proyecto JRuby utiliza en gran medida JnrFFI y es probable que forme la base para el nuevo Java FFI JEP . Esto básicamente elimina la escritura de todas las tonterías JNI. Aquí hay un código de ejemplo que usa JnrFFI para llamar a una función Rust desde Java:
Código 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);
}
Código de óxido
#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
input * 2
}
Aquí está el código completo