Use jni-rs to realize Rust and Android code calling each other

This article mainly introduces how to use jni-rs . The relevant jni-rscontent is based on version 0.20.0, and the new version is written differently.

Getting Started

In cross-compiling the Rust library and using it in Android and iOS, I briefly explained the jni-rs and demo codes, and now I will add some details.

First paste the previous sample code:

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()
    }
}
  • JNIEnvThe sum of the first two parameters of the method JClassis required.
  • Among them JNIEnvis the JVM interface, which is used to call the JVM method. get_stringFor example, the sum method in the example new_string.
  • JClassis a class with static methods. Will not be used, but still needs to have a parameter slot.
  • JStringand jstringType Description: JStringis jstringa lifecycle representation of the type. When using jni-rsJNI code, it is recommended to use JStringtypes, because it provides better type safety and convenience without having to worry about memory allocation and deallocation. When you need to JStringconvert a type to jstringa type, you use JString::into_raw()a method. For example, the above example can be used JStringas the return value, then the last line can be used directly output.

jni-rsThe types in Java correspond to the types in Java as follows:

jni-rs Java
jboolean boolean
jbyte byte
jchar char
jshort short
jint int
long long
jfloat float
jdouble double
jstring、JString String
jobject、JObject Object
jclass、JClass Object
cut off array
jbooleanArray boolean[]
jbyteArray byte[]
jcharArray char[]
jshortArray short[]
jintArray int[]
jlongArray long[]
jfloatArray float[]
jdoubleArray double[]
jobjectArray Object[]

The above types are basically written in the same way as C/C++. It should be noted that there are several types with capitalized initial letters.


As a side note, there is a Rust method in the example above rust_greeting. To call it, we use as_ptrthe method JavaStrcast c_chartype. Then return c_charthe result and convert it to JString, and finally call into_rawthe converted jstringtype to return. The reason why it is so troublesome is because we consider rust_greetingthe situation of iOS direct calling here. If iOS is not considered, but only for Android, then it can be simplified as:

    #[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()
    }

Advanced usage

The above is mainly Android calling Rust code. Let's see how to call Android code in Rust.

First of all, we need to understand the JNI field descriptor, which is an encoding of function return values ​​and parameters.

Descriptor type example
V void -
I int (I)VShow void function(int param)
B BTYE (B)VIndicates void function(btye param)
C char -
D double -
F float -
J long -
S short -
Z boolean -
[element_type one-dimensional array ([I)VShow void function(int[] param)
L classname ; kind Ljava/lang/String;Represents String
  • Note the object type, Lstarts with and ;ends with.
  • If it is a two-dimensional array, it is two [symbols. For example [[I, it means int[][].

1. callback

Let's implement the Android side first, and add a CallBackmethod of passing in the interface:

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);
}

The corresponding Rust part of the code is as follows:

#[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
}

参考

Guess you like

Origin blog.csdn.net/qq_17766199/article/details/130189168