Use jni-rs para darse cuenta de que el código de Rust y Android se llama entre sí

Este artículo presenta principalmente cómo usar jni-rs . El jni-rscontenido relevante se basa en la versión 0.20.0 y la nueva versión está escrita de manera diferente.

Empezando

Al compilar de forma cruzada la biblioteca Rust y usarla en Android e iOS, expliqué brevemente los códigos jni-rs y de demostración, y ahora agregaré algunos detalles.

Primero pegue el código de muestra anterior:

use std::os::raw::{
    
    c_char};
use std::ffi::{
    
    CString, CStr};

#[no_mangle]
pub extern fn rust_greeting(to: *const c_char) -> *mut c_char {
    
    
    let c_str = unsafe {
    
     CStr::from_ptr(to) };
    let recipient = match c_str.to_str() {
    
    
        Err(_) => "there",
        Ok(string) => string,
    };

    CString::new("Hello ".to_owned() + recipient).unwrap().into_raw()
}

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

    use super::*;
    use self::jni::JNIEnv;
    use self::jni::objects::{
    
    JClass, JString};
    use self::jni::sys::{
    
    jstring};

    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greeting(
        env: JNIEnv,
        _class: JClass,
        java_pattern: JString
    ) -> jstring {
    
    
        // Our Java companion code might pass-in "world" as a string, hence the name.
        let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());
        // Retake pointer so that we can use it below and allow memory to be freed when it goes out of scope.
        let world_ptr = CString::from_raw(world);
        let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");

        output.into_raw()
    }
}
  • JNIEnvSe requiere la suma de los dos primeros parámetros del método JClass.
  • Entre ellos JNIEnvse encuentra la interfaz JVM, que se utiliza para llamar al método JVM. get_stringPor ejemplo, el método de suma en el ejemplo new_string.
  • JClasses una clase con métodos estáticos. No se usará, pero aún necesita tener una ranura de parámetro.
  • JStringy jstringDescripción del tipo: JStringes jstringuna representación del ciclo de vida del tipo. Al usar jni-rscódigo JNI, se recomienda usar JStringtipos, ya que proporciona una mayor seguridad y comodidad de tipo sin tener que preocuparse por la asignación y desasignación de memoria. Cuando necesita JStringconvertir un tipo a jstringun tipo, usa JString::into_raw()un método. Por ejemplo, el ejemplo anterior se puede usar JStringcomo valor de retorno, luego la última línea se puede usar directamente output.

jni-rsLos tipos en Java corresponden a los tipos en Java de la siguiente manera:

jni-rs Java
jbooleano booleano
jbyte byte
jchar carbonizarse
jcorto corto
jint En t
largo largo
jflotar flotar
jdoble doble
jstring、JString Cadena
proyecto de trabajo, objeto de trabajo Objeto
jclass、JClass Objeto
cortar formación
matriz jbooleana booleano[]
matriz jbyte byte[]
jcharArray carbonizarse[]
jshortArray corto[]
jintArray En t[]
jlongArray largo[]
matriz jfloat flotar[]
jdoblearreglo doble[]
jobjectArray Objeto[]

Los tipos anteriores se escriben básicamente de la misma manera que C/C++. Cabe señalar que hay varios tipos con letras iniciales en mayúscula.


Como nota al margen, hay un método Rust en el ejemplo anterior rust_greeting. Para llamarlo, usamos as_ptrel método JavaStrcast c_chartype. Luego devuelva c_charel resultado y conviértalo a JString, y finalmente llame into_rawal tipo convertido jstringpara regresar. rust_greetingLa razón por la que es tan problemático es porque aquí consideramos la situación de las llamadas directas de iOS. Si no se considera iOS, sino solo para Android, entonces se puede simplificar como:

    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greeting(
        env: JNIEnv,
        _class: JClass,
        java_pattern: JString
    ) -> jstring {
    
    
        let world: String = env.get_string(java_pattern).expect("invalid pattern string").into();
        let output = env.new_string(format!("Hello, {}!", world)).expect("Couldn't create java string!");
        output.into_raw()
    }

Uso avanzado

Lo anterior es principalmente Android llamando al código Rust. Veamos cómo llamar al código de Android en Rust.

En primer lugar, debemos comprender el descriptor de campo JNI, que es una codificación de los valores y parámetros de retorno de la función.

descriptor tipo ejemplo
V vacío -
I En t (I)VMostrar función nula (int param)
B BTYE (B)VIndica función nula (btye param)
C carbonizarse -
D doble -
F flotar -
j largo -
S corto -
Z booleano -
[tipo de elemento matriz unidimensional ([I)VMostrar función nula (int[] param)
L nombre de clase; amable Ljava/lang/String;Representa cadena
  • Tenga en cuenta el tipo de objeto, Lcomienza con y ;termina con.
  • Si es una matriz bidimensional, son dos [símbolos. Por ejemplo [[I, significa int[][].

1. devolución de llamada

Primero implementemos el lado de Android y agreguemos un CallBackmétodo para pasar la interfaz:

public class RustGreetings {
    
    
	...

    private static native void greetingCallBack(final String pattern, CallBack callBack);

    public void sayHelloCallBack(String to, CallBack callBack) {
    
    
        greetingCallBack(to, callBack);
    }
}

public interface CallBack {
    
    
    void result(String str);
}

La parte correspondiente de Rust del código es la siguiente:

#[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greetingCallBack(
    env: JNIEnv,
    _class: JClass,
    java_pattern: JString,
    callback: JObject
) {
    
    
    let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());
    let world_ptr = CString::from_raw(world);
    let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");
    // 延时1s
    thread::sleep(Duration::from_millis(1000));
    env.call_method(callback, "result", "(Ljava/lang/String;)V", &[JValue::from(output)]).unwrap();
}
  • CallBack对应的类型是JObject
  • call_method是Rust调用Android方法的函数。
  • result是调用方法名,(Ljava/lang/String;)V括号内是调用方法的参数类型String,V是方法返回值。
  • &[JValue::from(output)]是传入的参数。没有参数就写&[]

调用代码测试一下:

val rust = RustGreetings()
System.out.println(rust.sayHello("World"))
rust.sayHelloCallBack("Rust") {
    
     str ->
    System.out.println(str)
}

调用结果:
在这里插入图片描述

2.指针

有时我们需要将Rust中产生的数据供多个方法使用。一种方法是使用全局变量,比如之前文中提到的lazy_static,我们将结构体保存在Map中,使用时从中获取。另一种方法我们可以将数据的指针返给Android端,后面使用时,可以再传给Rust端使用。下面我们介绍一下第二种方法。

这里我就直接用jni-rs的demo来说明。

public class RustGreetings {
    
    

    ...

    private static native long counterNew(HelloWorld callback);
    
    private static native void counterIncrement(long counterPtr);
    
    private static native void counterDestroy(long counterPtr);
}

public class HelloWorld {
    
    

    public void counterCallback(int count) {
    
    
        System.out.println("counterCallback: count = " + count);
    }
}

添加了三个方法,counterNew用来传入回调对象。counterIncrement是计数器的自增方法。counterDestroy来释放计数器。

对应Rust代码如下:

    struct Counter {
    
    
        count: i32,
        callback: GlobalRef,
    }
    
    impl Counter {
    
    
        pub fn new(callback: GlobalRef) -> Counter {
    
    
            Counter {
    
    
                count: 0,
                callback: callback,
            }
        }
    
        pub fn increment(&mut self, env: JNIEnv) {
    
    
            self.count = self.count + 1;
            env.call_method(
                &self.callback,
                "counterCallback",
                "(I)V",
                &[self.count.into()],
            )
            .unwrap();
        }
    }
    
    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterNew(
        env: JNIEnv,
        _class: JClass,
        callback: JObject,
    ) -> jlong {
    
    
        let global_ref = env.new_global_ref(callback).unwrap();
        let counter = Counter::new(global_ref);
    
        Box::into_raw(Box::new(counter)) as jlong
    }
    
    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterIncrement(
        env: JNIEnv,
        _class: JClass,
        counter_ptr: jlong,
    ) {
    
    
        let counter = &mut *(counter_ptr as *mut Counter);
    
        counter.increment(env);
    }
    
    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterDestroy(
        _env: JNIEnv,
        _class: JClass,
        counter_ptr: jlong,
    ) {
    
    
        let _boxed_counter = Box::from_raw(counter_ptr as *mut Counter);
    }

  • 代码通过将计数器结构体Counter的指针返回给Android端,实现了计数自增功能。
  • 指针我们通过Box生成,它的作用是将你的数据存储在堆上,然后在栈中保留一个智能指针指向堆上的数据。
  • 调用from_raw函数后,释放分配的内存.

调用部分代码:

long counterPtr = counterNew(new HelloWorld());

for (int i = 0; i < 5; i++) {
    
    
    counterIncrement(counterPtr);
}

counterDestroy(counterPtr);

结果如下:
在这里插入图片描述

3.单例

Android端示例代码如下:

package com.weilu.demo;

import android.util.Log;

public class Singleton {
    
    
    private Singleton() {
    
    }

    public static Singleton getInstance() {
    
    
        return Singleton.SingletonHolder.INSTANCE;
    }
    private static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }

    public void sayHello(){
    
    
        Log.d("RustDemo","Hello!");
    }
}

一个简单的单例,里面有一个sayHello方法。

然后对应的Rust部分代码如下:

 #[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_sayHello(
    env: JNIEnv,
    _class: JClass
) {
    
    
    let class = env.find_class("com/weilu/demo/Singleton").expect("can't find class Singleton");

    let instance_method_id = env.get_static_method_id(class, "getInstance", "()Lcom/weilu/demo/Singleton;")
        .expect("can't find method Singleton.getInstance");

    let instance = env.call_static_method_unchecked(class, instance_method_id, ReturnType::Object, &[])
        .expect("can't call method getInstance");

    let instance_obj = JObject::from(instance.l().unwrap());

    let say_hello = env.get_method_id(class, "sayHello", "()V").expect("can't call method sayHello");

    env.call_method_unchecked(instance_obj, say_hello, ReturnType::Primitive(Void), &[]).unwrap();
}
  • find_class,顾名思义查找Class。这里是获取Singleton类。
  • get_static_method_id,获取静态方法id。这里是获取getInstance方法。
  • call_static_method_unchecked,调用静态方法。这里是调用getInstance方法,获取Singleton单例。
  • get_method_id,获取方法id。这里是获取sayHello方法。
  • call_method_unchecked,调用方法。这里是调用sayHello方法。
  • ReturnType::ObjectReturnType::Primitive(Void)是方法的返回类型,对应代码中的Singletonvoid

同理,可以使用上述这一套调用任何存在的系统方法,例如调用Java中的 System.currentTimeMillis(); 方法获取时间戳:

#[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_currentTimeMillis(
    env: JNIEnv,
    _class: JClass
) -> jlong {
    
    
    let system = env.find_class("java/lang/System").unwrap();
    let result = env.call_static_method(system, "currentTimeMillis", "()J", &[]).unwrap();
    let time = result.j().unwrap();
    time as jlong
}

参考

Supongo que te gusta

Origin blog.csdn.net/qq_17766199/article/details/130189168
Recomendado
Clasificación