0%

Jni符号对照

Jni符号表

本文是之前博客文章

《使用rust写安卓库》

的延续。之前的文章主题是如何利用rust-jni库提供便于java使用的rust jni代码。本文在之前的基础上继续提供后续关于jni符号,或者type的说明。

起因

写rust端代码的时候,如果我们想返回一个java端的对象我们的rust代码大概会按照以下写法(仅为示例,不可运行):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[no_mangle]
#[allow(non_snake_case)]
pub extern "system" fn Java_JniApi_hello(env: JNIEnv, _: JClass, str: JString) -> jobject {
let str = env.get_string(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 Attribute,告知编译器相应行为的,和本文关联不大,不必关心
// 函数名的命名在文章开始提供的那一篇文章中已经说的很清楚

代码的后两行注释说明了一些和本文无关的情况。以上的代码主要是生成java中的StatusMessage对象,这个对象中有两个field,message 和 code。在java端的对应的类型分别为int,String。以上代码就是填充StatusMessage返回给message端。根据jni库的规则(https://docs.rs/jni/0.16.0/jni/),我们需要以下三步

  1. 定位StatusMessage的class(熟悉java的人应该知道$之后代表内部类)。
  2. alloc_object 分配内存
  3. 填充数据

以上三点是使用jni库的步骤。我们现在关心的点是

set_field中的参数代表什么?

根据jni库的文档,set_field有四个参数,第一个参数为class对象,第二个参数为具体filed的名字,第三个为type。第四个为需要具体填充的值。

本文的重点是关心

type到底是什么

本文要解决的问题:type是什么,这里的type该怎么填充?type的填充主要看java端的规定。我们不从这一头看,我们回头去看java端。下面展示一个使用javac -h命令之后我们得到的java头文件。一般来说,我们不需要java头文件,但是我们要写jni,头文件是必须的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class HelloWorld {
// This declares that the static `hello` method will be provided
// a native library.
private static native String hello(String input);

static {
// This actually loads the shared object that we'll be creating.
// The actual location of the .so or .dll may differ based on your
// platform.
System.loadLibrary("mylib");
}

// The rest is just regular ol' Java!
public static void main(String[] args) {
String output = HelloWorld.hello("josh");
System.out.println(output);
}
}

.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: hello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloWorld_hello
(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

生成的h文件之中,我们发现有一段注释,只解释Signature。

Signature为函数签名.

签名的格式为 (参数类型)返回值类型

所以参数为String,返回值为String。得到的类型就是示例中的签名样式。

下面给出type的参考表,或者使用专有名词,叫做Type Signatures

Type Signatures Java Type
Z boolean
B byte
C har
S short
I int
J long
F float
D double
Lfully-qualified-class fully-qualified-class
[ type type[]
( arg-types ) ret-type method type
解释其中几点
  1. L后面跟的是具体类型的路径 比如String 类型在signatures中就表现为 Ljava/lang/String
  2. [数组类型
  3. 最后一行表现的是signatuer的格式

按照以上规则举个例子

1
2
3
4
5
6

函数:
long f (int n, String s, int[] arr);

对应签名
(ILjava/lang/String;[I)J

注意 [ 只有单边。参数之间不间隔,只有具体类型才用;收尾

所以再对应到一开始提到的问题,type该怎么填写

1
2
code 对应 StatusMessage 中的 int 类型。所以写 "I"
message 对应 StatusMessage 中的 String 类型。所以写作 "Ljava/lang/String;"

再给出一个oracle的文章,它更好,更全面。无论是写rust还是写c/cpp的接口都参考它。

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp9502

全文完。