Подтвердить что ты не робот

Вызов ржавчины с 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? Обратите внимание, что я действительно новичок в подобных материалах, поэтому, пожалуйста, не стесняйтесь редактировать этот вопрос.

4b9b3361

Ответ 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
}

Вот полный код