この記事では主に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_string
sumメソッドですnew_string
。 JClass
静的メソッドを持つクラスです。使用されませんが、パラメーター スロットが必要です。JString
型のjstring
説明:型のライフサイクル表現JString
です。jstring
JNI コードを使用する場合はjni-rs
、型を使用することをお勧めしますJString
。これは、メモリの割り当てや割り当て解除を気にする必要がなく、型の安全性と利便性が向上するためです。JString
型をjstring
型に変換する必要がある場合は、JString::into_raw()
メソッドを使用します。たとえば、上記の例をJString
戻り値として使用し、最後の行を直接使用することができますoutput
。
jni-rs
Java の型は、次のように 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 のみが考慮される場合は、次のように簡略化できます。JavaStr
c_char
c_char
JString
into_raw
jstring
rust_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)V void関数(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::Object
、ReturnType::Primitive(Void)
是方法的返回类型,对应代码中的Singleton
、void
。
同理,可以使用上述这一套调用任何存在的系统方法,例如调用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
}