admin管理员组

文章数量:1405159

I have an Android app (written in Kotlin), with the processing code running in Rust. I'm trying to send List<String> data to the Rust back-end and interpret it as a Vec<String>. The size of the list is variable. I'm using JNI (Java Native Interface) for this.

Android (Kotlin)

On the Kotlin side I have the following function signature:

package eu.mypackage.rust

class SomeName {
    @Throws(IllegalArgumentException::class)
    external fun checkAnswer(answerInput: String, answerList: List<String>): Boolean
}

Which I call with:

val isCorrect = sn.checkAnswer(answerInput, answerList)

Rust

On the Rust side I have this draft function:

#[cfg(target_os = "android")]
#[allow(non_snake_case)]
pub mod android {
    extern crate jni;

    // This is the interface to the JVM  that we'll call the majority of our methods on.
    // @See /
    use self::jni::JNIEnv;

    // These objects are what you should use as arguments to your native function.
    // They carry extra lifetime information to prevent them escaping this context
    // and getting used after being GC'd.
    use self::jni::objects::{JClass, JString, JObject, JObjectArray};  // Not sure what is required

    // This is just a pointer. We'll be returning it from our function. 
    // We can't return one of the objects with lifetime information 
    // because the lifetime checker won't let us.
    use self::jni::sys::{jstring, jboolean, jobjectArray, jsize};  // Not sure what is required

    fn kotlin_list_to_rust_vec_string(env: &JNIEnv, java_array: &jobjectArray) -> Vec<String> {
        // TODO function to convert
    }

    #[no_mangle] // This keeps Rust from "mangling" the name so it is unique (crate).
    pub extern "system" fn Java_eu_mycompany_rust_MyPackage_checkAnswer<'local>(
        mut env: JNIEnv<'local>,
        // This is the class that owns our static method. It's not going to be used,
        // but still must be present to match the expected signature of a static native method.
        _class: JClass<'local>,
        answerInput: JString<'local>,
        answerList: JObjectArray<'local>,  // JObjectArray correct?
    ) -> jboolean
    // Keep in mind that arrays of jboolean values should only ever hold values of 0 or 1 because any other value could lead to undefined behaviour within the JVM.
    // .JNIEnv.html#jboolean-elements
    {
        let answer_input: String = env.get_string(&answerInput).expect("Couldn't get java string!").into();
        let answer_list = kotlin_list_to_rust_vec_string(&env, &answerList);

        // ... code to process answer_list
        
        return 1 // true or 0 for false
    }
}

Question

  1. It seems there is no direct conversion for Java List<String> -> Rust Vec<String>, so a function like kotlin_list_to_rust_vec_string is required?
  2. What code should be in kotlin_list_to_rust_vec_string? I assume env and answerList should be a reference to avoid screwing up the data on the Kotlin side? (The amount of data is small, so copying the data is fine)

I have an Android app (written in Kotlin), with the processing code running in Rust. I'm trying to send List<String> data to the Rust back-end and interpret it as a Vec<String>. The size of the list is variable. I'm using JNI (Java Native Interface) for this.

Android (Kotlin)

On the Kotlin side I have the following function signature:

package eu.mypackage.rust

class SomeName {
    @Throws(IllegalArgumentException::class)
    external fun checkAnswer(answerInput: String, answerList: List<String>): Boolean
}

Which I call with:

val isCorrect = sn.checkAnswer(answerInput, answerList)

Rust

On the Rust side I have this draft function:

#[cfg(target_os = "android")]
#[allow(non_snake_case)]
pub mod android {
    extern crate jni;

    // This is the interface to the JVM  that we'll call the majority of our methods on.
    // @See https://docs.rs/jni/latest/jni/
    use self::jni::JNIEnv;

    // These objects are what you should use as arguments to your native function.
    // They carry extra lifetime information to prevent them escaping this context
    // and getting used after being GC'd.
    use self::jni::objects::{JClass, JString, JObject, JObjectArray};  // Not sure what is required

    // This is just a pointer. We'll be returning it from our function. 
    // We can't return one of the objects with lifetime information 
    // because the lifetime checker won't let us.
    use self::jni::sys::{jstring, jboolean, jobjectArray, jsize};  // Not sure what is required

    fn kotlin_list_to_rust_vec_string(env: &JNIEnv, java_array: &jobjectArray) -> Vec<String> {
        // TODO function to convert
    }

    #[no_mangle] // This keeps Rust from "mangling" the name so it is unique (crate).
    pub extern "system" fn Java_eu_mycompany_rust_MyPackage_checkAnswer<'local>(
        mut env: JNIEnv<'local>,
        // This is the class that owns our static method. It's not going to be used,
        // but still must be present to match the expected signature of a static native method.
        _class: JClass<'local>,
        answerInput: JString<'local>,
        answerList: JObjectArray<'local>,  // JObjectArray correct?
    ) -> jboolean
    // Keep in mind that arrays of jboolean values should only ever hold values of 0 or 1 because any other value could lead to undefined behaviour within the JVM.
    // https://docs.rs/jni/latest/jni/struct.JNIEnv.html#jboolean-elements
    {
        let answer_input: String = env.get_string(&answerInput).expect("Couldn't get java string!").into();
        let answer_list = kotlin_list_to_rust_vec_string(&env, &answerList);

        // ... code to process answer_list
        
        return 1 // true or 0 for false
    }
}

Question

  1. It seems there is no direct conversion for Java List<String> -> Rust Vec<String>, so a function like kotlin_list_to_rust_vec_string is required?
  2. What code should be in kotlin_list_to_rust_vec_string? I assume env and answerList should be a reference to avoid screwing up the data on the Kotlin side? (The amount of data is small, so copying the data is fine)
Share Improve this question edited Mar 13 at 7:20 NumesSanguis asked Mar 9 at 7:44 NumesSanguisNumesSanguis 6,4028 gold badges46 silver badges81 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

Rust

With the help of a LLM, I was able to produce the following solution:

    fn kotlin_list_to_rust_vec_string(env: &mut JNIEnv, java_array: &JObjectArray) -> Vec<String> {
        let string_count = env.get_array_length(java_array).unwrap();

        let mut rust_strings = Vec::with_capacity(string_count as usize);

        for i in 0..string_count {
            let string_element = env.get_object_array_element(&java_array, i).unwrap();
            let java_string: JString = string_element.into();
            let rust_string = env.get_string(&java_string).unwrap().into();
            rust_strings.push(rust_string);
        }

        rust_strings
    }

The env variable had to be mutable for this to work though (not sure if this is a safe way of doing it?), so call the function with:

let answer_list = kotlin_list_to_rust_vec_string(&mut env, &answerList);

Android (Kotlin)

While the code compiled on the Rust side, my Android app crashed with the error:

java_vm_ext:598] JNI DETECTED ERROR IN APPLICATION: jarray argument has non-array type: java.util.ArrayList
java_vm_ext:598]     in call to GetArrayLength
java_vm_ext:598]     from boolean eu.mypackage.rust.

The LLM explained the error as:

In Kotlin, when you pass a List<String> to a native method, it remains a java.util.ArrayList, which is not a JNI-compatible array. Solution: Convert the List<String> to a String[] before passing it to Rust

So updating the function signature:

    @Throws(IllegalArgumentException::class)
    // in Kotlin, when you pass a List<String> to a native method, it remains a java.util.ArrayList,
    // which is not a JNI-compatible array. Convert the List<String> to a String[]
    external fun checkAnswer(answerInput: String, answerList: Array<String>): Boolean  // List<String>

and adding .toTypedArray() to my function call:

val isCorrect = sn.checkAnswer(answerInput, answerList.toTypedArray())  // convert to Array

Solved the final issue.

本文标签: