Llamando a Rust desde Java
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 clase Java tests.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 Java main se muestra el resultado esperado:
hello from rust
42
En los métodos Rust declaré env
como un puntero al tipo Any
pero en realidad es una estructura C con punteros a 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 respuesta 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 traté de implementar una estructura de este tipo estableciendo todos los valores de campo a *mut Any
excepto para 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 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.
2 answers
Aparte del problema que *mut Any
/*const Any
son punteros fat, también hay un hecho de que las funciones nativas JNI utilizan doble indirección al acceder a JNINativeInterface
estructura:
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 de libc externa):
#![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 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 puedes omitir el campo _opaque_data
ya que la estructura JNINativeInterface
siempre se pasa por puntero, por lo que si solo necesitas varios primero campos de la estructura, puedes declararlos solo e ignorar el resto.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-15 12:14:04
Un enfoque más sencillo 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 escribir todas las tonterías del JNI. Aquí está 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 Rust
#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
input * 2
}
Aquí está el código completo
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-20 04:49:05