jni-rs を使用して Rust と Android コードの相互呼び出しを実現する

この記事では主にjni-rsの使い方を紹介します当該jni-rs内容はバージョン0.20.0をベースにしており、新バージョンでは記述が異なります。

入門

Rust ライブラリのクロスコンパイルと Android および iOS での使用について、 jni -rsとデモ コードについて簡単に説明しましたが、ここでさらに詳細を追加します。

まず、前のサンプル コードを貼り付けます。

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()
    }
}
  • メソッドの最初の 2 つのパラメーターのJNIEnv合計JClassが必要です。
  • その中にはJNIEnv、JVM メソッドを呼び出すために使用される JVM インターフェイスがあります。たとえば、例のget_stringsumメソッドですnew_string
  • JClass静的メソッドを持つクラスです。使用されませんが、パラメーター スロットが必要です。
  • JString型のjstring説明:型のライフサイクル表現JStringです。jstringJNI コードを使用する場合はjni-rs、型を使用することをお勧めしますJString。これは、メモリの割り当てや割り当て解除を気にする必要がなく、型の安全性と利便性が向上するためです。JString型をjstring型に変換する必要がある場合は、JString::into_raw()メソッドを使用します。たとえば、上記の例をJString戻り値として使用し、最後の行を直接使用することができますoutput

jni-rsJava の型は、次のように Java の型に対応します。

ジュニアーズ ジャワ
jboolean ブール値
ジバイト バイト
ジチャー 文字
ジェイショート 短い
ジント 整数
長さ 長さ
jfloat 浮く
ジェイダブル ダブル
jstring、JString
ジョブプロジェクト、JObject 物体
jクラス、Jクラス 物体
切り落とす 配列
jbooleanArray ブール値[]
jbyte配列 バイト[]
jchar配列 文字[]
jshort配列 短い[]
jintArray int[]
jlong​​配列 長さ[]
jfloat配列 浮く[]
jdoubleArray ダブル[]
ジョブプロジェクト配列 物体[]

上記の型は基本的にC/C++と同様に記述できますが、頭文字が大文字の型がいくつかあることに注意してください。


余談ですが、上記の例には Rust メソッドがありますrust_greetingこれを呼び出すには、キャスト型as_ptrメソッドを使用します次に、結果を返して に変換し、最後に変換された型を呼び出して返します。なぜ面倒かというと、ここでは iOS の直接呼び出しの状況を考慮しているからです。iOS が考慮されず、Android のみが考慮される場合は、次のように簡略化できます。JavaStrc_charc_charJStringinto_rawjstringrust_greeting

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

高度な使用法

上記は主に Android が Rust コードを呼び出しているものです。Rust で Android コードを呼び出す方法を見てみましょう。

まず第一に、関数の戻り値とパラメーターのエンコードである JNI フィールド記述子を理解する必要があります。

ディスクリプタ タイプ
V 空所 -
整数 (I)V 表示 void function(int param)
B ところで (B)Vvoid関数(btye param)を示します
C 文字 -
D ダブル -
F 浮く -
J 長さ -
S 短い -
Z ブール値 -
[要素の種類 一次元配列 ([I)V 表示 void function(int[] param)
L クラス名 ; 親切 Ljava/lang/String;文字列を表します
  • オブジェクトの種類、Lで始まり、で;終わることに注意してください。
  • 2 次元配列の場合は 2 つの[シンボルです。たとえば[[I、 という意味ですint[][]

1.コールバック

まず Android 側を実装し、CallBackインターフェースに渡すメソッドを追加しましょう。

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

コードの対応する Rust 部分は次のとおりです。

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

参考

おすすめ

転載: blog.csdn.net/qq_17766199/article/details/130189168