java jni rust

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