Este artículo presenta principalmente cómo usar jni-rs . El jni-rs
contenido relevante se basa en la versión 0.20.0 y la nueva versión está escrita de manera diferente.
Empezando
Al compilar de forma cruzada la biblioteca Rust y usarla en Android e iOS, expliqué brevemente los códigos jni-rs y de demostración, y ahora agregaré algunos detalles.
Primero pegue el código de muestra anterior:
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
Se requiere la suma de los dos primeros parámetros del métodoJClass
.- Entre ellos
JNIEnv
se encuentra la interfaz JVM, que se utiliza para llamar al método JVM.get_string
Por ejemplo, el método de suma en el ejemplonew_string
. JClass
es una clase con métodos estáticos. No se usará, pero aún necesita tener una ranura de parámetro.JString
yjstring
Descripción del tipo:JString
esjstring
una representación del ciclo de vida del tipo. Al usarjni-rs
código JNI, se recomienda usarJString
tipos, ya que proporciona una mayor seguridad y comodidad de tipo sin tener que preocuparse por la asignación y desasignación de memoria. Cuando necesitaJString
convertir un tipo ajstring
un tipo, usaJString::into_raw()
un método. Por ejemplo, el ejemplo anterior se puede usarJString
como valor de retorno, luego la última línea se puede usar directamenteoutput
.
jni-rs
Los tipos en Java corresponden a los tipos en Java de la siguiente manera:
jni-rs | Java |
---|---|
jbooleano | booleano |
jbyte | byte |
jchar | carbonizarse |
jcorto | corto |
jint | En t |
largo | largo |
jflotar | flotar |
jdoble | doble |
jstring、JString | Cadena |
proyecto de trabajo, objeto de trabajo | Objeto |
jclass、JClass | Objeto |
cortar | formación |
matriz jbooleana | booleano[] |
matriz jbyte | byte[] |
jcharArray | carbonizarse[] |
jshortArray | corto[] |
jintArray | En t[] |
jlongArray | largo[] |
matriz jfloat | flotar[] |
jdoblearreglo | doble[] |
jobjectArray | Objeto[] |
Los tipos anteriores se escriben básicamente de la misma manera que C/C++. Cabe señalar que hay varios tipos con letras iniciales en mayúscula.
Como nota al margen, hay un método Rust en el ejemplo anterior rust_greeting
. Para llamarlo, usamos as_ptr
el método JavaStr
cast c_char
type. Luego devuelva c_char
el resultado y conviértalo a JString
, y finalmente llame into_raw
al tipo convertido jstring
para regresar. rust_greeting
La razón por la que es tan problemático es porque aquí consideramos la situación de las llamadas directas de iOS. Si no se considera iOS, sino solo para Android, entonces se puede simplificar como:
#[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()
}
Uso avanzado
Lo anterior es principalmente Android llamando al código Rust. Veamos cómo llamar al código de Android en Rust.
En primer lugar, debemos comprender el descriptor de campo JNI, que es una codificación de los valores y parámetros de retorno de la función.
descriptor | tipo | ejemplo |
---|---|---|
V | vacío | - |
I | En t | (I)V Mostrar función nula (int param) |
B | BTYE | (B)V Indica función nula (btye param) |
C | carbonizarse | - |
D | doble | - |
F | flotar | - |
j | largo | - |
S | corto | - |
Z | booleano | - |
[tipo de elemento | matriz unidimensional | ([I)V Mostrar función nula (int[] param) |
L nombre de clase; | amable | Ljava/lang/String; Representa cadena |
- Tenga en cuenta el tipo de objeto,
L
comienza con y;
termina con. - Si es una matriz bidimensional, son dos
[
símbolos. Por ejemplo[[I
, significaint[][]
.
1. devolución de llamada
Primero implementemos el lado de Android y agreguemos un CallBack
método para pasar la interfaz:
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);
}
La parte correspondiente de Rust del código es la siguiente:
#[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
}