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.

Author: Community, 2015-05-15

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 JNINativeInterfacesiempre se pasa por puntero, por lo que si solo necesitas varios primero campos de la estructura, puedes declararlos solo e ignorar el resto.

 17
Author: Vladimir Matveev,
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

 9
Author: Suresh G,
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