0%

使用状态码加消息传递库状态

这篇文章是上一篇使用rust写安卓库的续篇,本身按照上一篇文章的介绍已经完全基本讲清楚了使用rust给安卓写库的基本方面,这篇文章继续在之前的状态上补充,力求文章完备。

本文要解决的场景如下

当我们外部调用rust库时难免出现库本身状态出现问题,比如内部错误,比如参数错误。假如我们在只使用同一种语言,这种问题很好处理,善用语言错误处理机制即可。然而我们跨语言就出现了问题,如在之前文章设定的场景:java端的错误是Exception,rust端的错误是Result和Option。

所以我们采用状态码来传递错误信息。着重于传递错误信息。我们把想传递的消息给调用端即可。毕竟作为库的使用者,并不知道库内部的具体信息,只需要知道我作为传递信息者有什么问题就好了。库的问题由库来解决。

在设定以上场景之后,我们的问题就很容易解决。除去错误处理这个杂质,进一步简化问题为如下

调用端传递信息进库,库返回状态(对象)给的调用者。

唯一的问题就是跨语言。(这个属于具体使用rust-jni库的细节,如果要写一个跨语言的库,大体是这种处理方案)

然后我们来写代码(设定场景,java调用rust库。)

首先在java端定义错误本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 class JniApi {

public class StatusMessage {
public int code;
public String message;
}

private static native StatusMessage send_message(String mnemonic_str);

static {
System.loadLibrary("murmel");
}

// The rest is just regular ol' Java!
public static void main(String[] args) {
StatusMessage message1 = JniApi.sned_message("Yes");
System.out.println("------------------------------");
System.out.println(message1.message);
}
}

注意,以上的代码并不符合java规范。只是可用。如果规范,请给StatusMessage实现toString()方法。本文为了简化,没有写。

StatusMessage定义了消息本身,它很简单,一个状态码和一个状态消息。定义静态方法,返回StatusMessage本身。然后在main中调用静态方法。最后打印返回的StatusMessage对象。

接下来在rust端实现send_message方法本身

1
2
3
4
5
6
7
8
9
10
11
12
13
#[no_mangle]
pub extern "system" fn Java_JniApi_send_message(env: JNIEnv, _: JClass, str: JString) -> jobject {
let str = env.get_string(mnemonic_str).unwrap();

let message_class = env.find_class("JniApi$StatusMessage").expect("can't find class JniApi$StatusCode");
let message_obj = env.alloc_object(message_class).expect("create message instance error");

env.set_field(message_obj, "code", "I", JValue::Int(200)).expect("set code error");
env.set_field(message_obj, "message", "Ljava/lang/String;", JValue::Object(JObject::from(env.new_string("Rust".to_string()).unwrap()))).expect("set error msg value is error!");


message_obj.into_inner()
}

对上面代码做简单说明。

首先,rust方法的命名来自于对java文件得到javac -h 操作。这个上一篇文章提到过。JNIEnv和JClass这两个是固定的参数,第三个参数是JString对应,静态方法中传入的字符串型参数。

代码得到正文是获取输入参数,他的使用方法是固定的,使用env参数获取

接下来我们填充需要返回的StatusMessage,所有的java class在rust端都被jni-rs抽象为jobject。

第一步,我们寻找class。我们使用env.fin_class()方法完成这一点。其中的参数代表类的名字。如果熟悉java,我们知道JniApi$StatusMessage,这种写法代表内部类。在javac -h生成的文件中我们也可以发现这一点。

第二步,分配内存。我们使用env.alloc_object()来实现

第三步,填充field。因为在java,我们把class持有的数据称为filed。具体参数请查看jni-rs的文档。单独提出来两点,对于int,我们理解为JValue::Int,对于的对象类型(java端的String)我们使用了JValue::Object。他们属于jni-rs使用的细节。

最后返回jobject对象即可

最后在总结成四个步骤

  1. 定位class
  2. 分配内存
  3. 填充field
  4. 返回jobject

以上已经完全说清楚如何返回给使用端一个java可以理解的”对象“。更复杂的情况也是以上四步大象装进冰箱法。当然其实这些都属于jni-rs的使用细节,我只是单独把它拿出来讲。

希望通过这两篇文章把如何使用rust给安卓写动态库的问题讲清楚。前一篇种配置和基本思路,这篇讲具体细节。