您现在的位置是:首页 >学无止境 >使用jni-rs实现Rust与Android代码互相调用网站首页学无止境
使用jni-rs实现Rust与Android代码互相调用
本篇主要是介绍如何使用jni-rs。有关jni-rs
内容基于版本0.20.0,新版本写法有所不同。
入门用法
在Rust库交叉编译以及在Android与iOS中使用中我简单说明了jni-rs及demo代码,现在接着补充一些详细内容。
首先贴上之前的示例代码:
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()
}
}
- 方法的前两个参数
JNIEnv
和JClass
是必须的。 - 其中
JNIEnv
是JVM接口,用来调用JVM方法。比如例子中的get_string
和new_string
方法。 JClass
是静态方法的类。不会被使用,但仍需要有一个参数槽。JString
和jstring
类型说明:JString
是jstring
的生命周期表现形式。在使用jni-rs
编写JNI代码时,建议使用JString
类型,因为它提供了更好的类型安全性和方便性,而不必担心内存分配和释放问题。当需要将JString
类型转换为jstring
类型时,可以使用JString::into_raw()
方法。例如上面的例子,可以使用JString
作为返回值,那么最后一行可以直接使用output
。
jni-rs
中的类型与Java中的类型对应如下:
jni-rs | Java |
---|---|
jboolean | boolean |
jbyte | byte |
jchar | char |
jshort | short |
jint | int |
jlong | long |
jfloat | float |
jdouble | double |
jstring、JString | String |
jobject、JObject | Object |
jclass、JClass | Object |
jarray | 数组 |
jbooleanArray | boolean[] |
jbyteArray | byte[] |
jcharArray | char[] |
jshortArray | short[] |
jintArray | int[] |
jlongArray | long[] |
jfloatArray | float[] |
jdoubleArray | double[] |
jobjectArray | Object[] |
上面类型基本与C/C++写法一致,需要注意的是有几个首字母大写形式的类型。
另外补充一点,上面的例子中存在一个Rust方法rust_greeting
。为了调用它,我们用到了as_ptr
方法将JavaStr
转为c_char
类型。然后返回c_char
结果再转为JString
,最后调用into_raw
转换为jstring
类型返回。之所以这么麻烦,因为我们这里考虑到iOS直接调用rust_greeting
的情况。如果不考虑iOS,只是用于Android端,那么可以简化为:
#[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字段描述符,JNI字段描述符是一种对函数返回值和参数的编码。
描述符 | 类型 | 例子 |
---|---|---|
V | void | - |
I | int | (I)V 表示 void function(int param) |
B | btye | (B)V 表示 void function(btye param) |
C | char | - |
D | double | - |
F | float | - |
J | long | - |
S | short | - |
Z | boolean | - |
[element_type | 一维数组 | ([I)V 表示 void function(int[] param) |
L classname ; | 类 | Ljava/lang/String; 表示String |
- 注意对象类型,以
L
开头,以;
结尾。 - 如果是二维数组,就是两个
[
符号。比如[[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
}