This article mainly introduces how to use jni-rs . The relevant jni-rs
content 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()
}
}
JNIEnv
The sum of the first two parameters of the methodJClass
is required.- Among them
JNIEnv
is the JVM interface, which is used to call the JVM method.get_string
For example, the sum method in the examplenew_string
. JClass
is a class with static methods. Will not be used, but still needs to have a parameter slot.JString
andjstring
Type Description:JString
isjstring
a lifecycle representation of the type. When usingjni-rs
JNI code, it is recommended to useJString
types, because it provides better type safety and convenience without having to worry about memory allocation and deallocation. When you need toJString
convert a type tojstring
a type, you useJString::into_raw()
a method. For example, the above example can be usedJString
as the return value, then the last line can be used directlyoutput
.
jni-rs
The 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_ptr
the method JavaStr
cast c_char
type. Then return c_char
the result and convert it to JString
, and finally call into_raw
the converted jstring
type to return. The reason why it is so troublesome is because we consider rust_greeting
the 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)V Show void function(int param) |
B | BTYE | (B)V Indicates void function(btye param) |
C | char | - |
D | double | - |
F | float | - |
J | long | - |
S | short | - |
Z | boolean | - |
[element_type | one-dimensional array | ([I)V Show void function(int[] param) |
L classname ; | kind | Ljava/lang/String; Represents String |
- Note the object type,
L
starts with and;
ends with. - If it is a two-dimensional array, it is two
[
symbols. For example[[I
, it meansint[][]
.
1. callback
Let's implement the Android side first, and add a CallBack
method 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::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
}