赞
踩
本篇主要是介绍如何使用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[][]
。我们先实现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)
}
调用结果:
有时我们需要将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);
结果如下:
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
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。