一个例子掌握JNI开发

继续上一篇博文[eclipse搭建JNI开发环境](/blog/2016-03-21/),现在我们从代码角度分析,C和Java混合编程时能实现的功能。

jni头文件

使用javah命令,编译生成.h头文件时,每个函数,至少都会有两个参数。JNIEnv jclass/jobject。其中,当native方法是静态方法(类方法)时,第二个参数是jclass,当native方法是成员方法时,第二个参数是jobject。其余的参数,会根据你在java文件中声明的方法参数类型,生成具体的签名。jni中类型在jni头文件中定义规则如下:

1
2
3
4
5
6
7
8
9
10
11
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;

对应签名:

java类型 jni类型 类型签名
char jchar C
int jint I
long jlong J
float jfloat F
double jdouble D
boolean jboolean Z
byte jbyte B
short jshort S
void V
L全限定名;,比如String, 其签名为Ljava/lang/util/String;
数组 [类型签名, 比如 [B

Jni.java 文件中,对应7个native方法。

  1. 调用C语言的printf函数,输出固定内容。

    1
    public static native void print();
  2. 转入指定字符串,用printf函数输出。

    1
    public static native void print(String str);
  3. 用C语言实现拼接字符串的功能,并返回给java。

    1
    public static native String append(String str);
  4. 传入字符串,作为Test类构造函数的函数,C语言调用Java类的构造函数,生成jobject,操纵Test类的所有方法和属性。

    1
    public native void test(String test);
  5. 传入Test类的对象,操纵操纵Test类的所有方法和属性。

    1
    public native void test(Test test);
  6. 将传入的字节数组转16进制字符串返回。

    1
    public native String toHex(byte[] test);
  7. 将传入的字符串转成16进制字节数组返回。

    1
    public native byte[] toBytes(String test);

完整示例代码

com_flueky_jni_Jni.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_flueky_jni_Jni */

#ifndef _Included_com_flueky_jni_Jni
#define _Included_com_flueky_jni_Jni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_flueky_jni_Jni
* Method: print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__
(JNIEnv *, jclass);

/*
* Class: com_flueky_jni_Jni
* Method: print
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__Ljava_lang_String_2
(JNIEnv *, jclass, jstring);

/*
* Class: com_flueky_jni_Jni
* Method: append
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_append
(JNIEnv *, jclass, jstring);

/*
* Class: com_flueky_jni_Jni
* Method: test
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Ljava_lang_String_2
(JNIEnv *, jobject, jstring);

/*
* Class: com_flueky_jni_Jni
* Method: test
* Signature: (Lcom/flueky/jni/Test;)V
*/
JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Lcom_flueky_jni_Test_2
(JNIEnv *, jobject, jobject);

/*
* Class: com_flueky_jni_Jni
* Method: toHex
* Signature: ([B)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_toHex
(JNIEnv *, jobject, jbyteArray);

/*
* Class: com_flueky_jni_Jni
* Method: toBytes
* Signature: (Ljava/lang/String;)[B
*/
JNIEXPORT jbyteArray JNICALL Java_com_flueky_jni_Jni_toBytes
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
* main.cpp
*
* Created on: 2016年3月22日
* Author: flueky
*/

#include <stdio.h>
#include "com_flueky_jni_Jni.h"
#include <jni.h>
#include <stdlib.h>
#include <string.h>
/**
* 操作test类的对象
*/
void operate_test(JNIEnv *env, jobject obj) {
//根据对象,获取到jclass
jclass test_cls = env->GetObjectClass(obj);
//获取成员方法id
jmethodID get_mid = env->GetMethodID(test_cls, "getTest",
"()Ljava/lang/String;");
//回调成员方法
jstring test = (jstring) env->CallObjectMethod(obj, get_mid);
jsize len = env->GetStringUTFLength(test);
const char* str = env->GetStringUTFChars(test, JNI_FALSE);
char* result = (char*) malloc(sizeof(char) * (len + 1));
strcpy(result, str);
//标志结束
*(result + len) = 0;
printf("getTest 输出:%s\n", result);
//获取append方法id,调用append方法
jmethodID append_mid = env->GetMethodID(test_cls, "append",
"(Ljava/lang/String;)V");
env->CallVoidMethod(obj, append_mid, env->NewStringUTF("append test"));
printf("append: append test\n");
//获取成员变量id,类变量id GetStaticFieldID
jfieldID test_fid = env->GetFieldID(test_cls, "test", "Ljava/lang/String;");
//获取成员变量值
test = (jstring) env->GetObjectField(obj, test_fid);
len = env->GetStringUTFLength(test);
str = env->GetStringUTFChars(test, JNI_FALSE);
result = (char*) malloc(sizeof(char) * (len + 1));
strcpy(result, str);
//标志结束
*(result + len) = 0;
printf("append 结果:%s\n", result);

//获取静态方法id
jmethodID print_mid = env->GetStaticMethodID(test_cls, "print",
"(ICFZLjava/lang/String;)V");
//调用静态方法
env->CallStaticVoidMethod(test_cls, print_mid, 1, 'c', 1.2f, true, test);

//删除obj对象
env->DeleteLocalRef(obj);
env->DeleteLocalRef(test);

}

JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__(JNIEnv *env,
jclass cls) {
printf("小飞哥0217\n");
}

JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__Ljava_lang_String_2(
JNIEnv *env, jclass cls, jstring jstr) {

jsize len = env->GetStringUTFLength(jstr);
const char* str = env->GetStringUTFChars(jstr, JNI_FALSE);
char* result = (char*) malloc(sizeof(char) * (len + 1));
strcpy(result, str);
//标志结束
*(result + len) = 0;
printf("本地输出:%s", result);
}

JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_append(JNIEnv *env, jclass,
jstring jstr) {
//获取jstring 的长度
jsize len = env->GetStringUTFLength(jstr);
//jstring 转字符串数组
const char* str = env->GetStringUTFChars(jstr, JNI_FALSE);
//分配结果字符串空间
char* result = (char*) malloc(sizeof(char) * (len + 7 + 1));
//字符串函数处理
strcpy(result, "append ");
strcpy(result + 7, str);
//标志结束
*(result + 7 + len) = 0;
return env->NewStringUTF(result);
}

/**
* 操作test类
*/
JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Ljava_lang_String_2(
JNIEnv *env, jobject obj, jstring jstr) {
//Test类
jclass test_cls = env->FindClass("com/flueky/jni/Test");
//Test类的构造方法id,构造方法名固定<init>,返回类型void
jmethodID init_mid = env->GetMethodID(test_cls, "<init>",
"(Ljava/lang/String;)V");
//创建Test对象
jobject test_obj = env->NewObject(test_cls, init_mid, jstr);

operate_test(env, test_obj);

}
JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Lcom_flueky_jni_Test_2(
JNIEnv *env, jobject obj, jobject test_obj) {
operate_test(env, test_obj);
}

JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_toHex(JNIEnv *env,
jobject jobj, jbyteArray jbytes) {
//获取字符串长度
jsize len = env->GetArrayLength(jbytes);
//分配结果的内存
char *result = (char *) malloc(sizeof(char) * (len * 2 + 1));
//分配缓存的内存
jbyte* temp = (jbyte *) malloc(sizeof(jbyte) * len);
//从字节数组中取字符
env->GetByteArrayRegion(jbytes, 0, len, temp);
//转16进制
for (int i = 0; i < len; i++) {
*(result + i * 2) = ((*(temp + i) >> 4) & 0xf) + '0';
*(result + i * 2 + 1) = (*(temp + i) & 0xf) + '0';
}
//释放缓存的内存
free(temp);
*(result + len * 2) = 0;
//生成jstring
jstring str = env->NewStringUTF(result);
free(result);
return str;
}

JNIEXPORT jbyteArray JNICALL Java_com_flueky_jni_Jni_toBytes(JNIEnv *env,
jobject jobj, jstring jstr) {
//获取字符串长度
jsize len = env->GetStringUTFLength(jstr);
//分配字节数组空间
jbyteArray jbytes = env->NewByteArray(len * 2);
//将jstring转成字符数组
const jchar * temp = env->GetStringChars(jstr, JNI_FALSE);
//分配结果的内存
char *result = (char *) malloc(sizeof(char) * (len * 2));
//转16进制
for (int i = 0; i < len; i++) {
*(result + i * 2) = ((*(temp + i) >> 4) & 0xf) + '0';
*(result + i * 2 + 1) = (*(temp + i) & 0xf) + '0';
}
//将字符存到字节数组里
env->SetByteArrayRegion(jbytes, 0, len * 2, (const jbyte *) result);
free(result);
return jbytes;
}

Jni.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.flueky.jni;

public class Jni {

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

/**
* 本地方法,用C语言实现
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午4:23:00
*/
public static native void print();

/**
* 本地方法,用C语言实现
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午6:10:43
* @param str
*/
public static native void print(String str);

/**
* 拼接字符传并返回
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午6:12:03
* @param str
* @return
*/
public static native String append(String str);

/**
* 测试操作Test类
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午6:16:06
* @param test
*/
public native void test(String test);

/**
* 测试操作Test
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午6:16:59
* @param test
*/
public native void test(Test test);

/**
* 将test 转16进制
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午6:25:06
* @param test
* @return
*/
public native String toHex(byte[] test);

/**
* 将test转字节
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午6:25:17
* @param test
* @return
*/
public native byte[] toBytes(String test);

}

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.flueky.jni;

public class Test {

private String test;

public Test(String test) {
super();
this.test = test;
}

public String getTest() {
return test;
}

public void append(String str) {
this.test = test + " " + str;
}

/**
* 测试调用静态方法,多参数
*
* @author flueky flueky@sina.com
* @date 2016年3月22日 下午6:19:13
* @param str
*/
public static void print(int i, char c, float f, boolean z, String test) {
System.out.println(String.format("Test printf:int = %d,char = %c,float = %.2f,boolean = %s,test = %s", i, c, f,
z + "", test));
}

}

main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.flueky.jni;

public class Main {

public static void main(String[] args) {

Jni.print();// 小飞哥0217
Jni.print("csdn 测试");// 本地输出:csdn 测试
System.out.println(Jni.append("flueky"));// append flueky

Jni jni = new Jni();

jni.test(new Test("小飞哥0217"));
jni.test("CSCN 测试");

System.out.println(new String(jni.toBytes("ABCDE")));
System.out.println(jni.toHex("12345".getBytes()));
}

}

Jni方法说明

  1. 获取jclass对象:

    a.env->FindClass(“com/flueky/jni/Test”);注意,这里不是类的签名。

    b.env->GetObjectClass(obj);

  2. 获取方法id:

    a.env->GetMethodID(test_cls, “getTest”,”()Ljava/lang/String;”);//获取成员方法id

    b.env->GetStaticMethodID(test_cls, “print”,”(ICFZLjava/lang/String;)V”);//获取静态方法id

    第一个参数,jclass对象,第二个参数方法名称,第三个参数,方法签名

  3. 调用方法:

    a.env->CallVoidMethod(obj, append_mid, env->NewStringUTF(“append test”));//调用成员方法

    第一个参数jobject,第二个参数方法id,后面参数,依次是Java方法中的参数。

    b.env->CallStaticVoidMethod(test_cls, print_mid, 1, ‘c’, 1.2f, true, test);//调用静态方法

    第一个参数jclass,第二个参数方法id,后面参数,依次是Java方法中的参数。

  4. 获取属性id:

    a.env->GetFieldID(test_cls, “test”, “Ljava/lang/String;”);//获取成员属性id

    b.env->GetStaticFieldID(test_cls, “test”, “Ljava/lang/String;”);//获取静态属性id,程序里没用到。

    第一个参数jclass,第二个参数属性名称,第三个参数属性签名

  5. 获取属性值:

    a.env->GetObjectField(obj, test_fid);

    第一个参数,jobject,第二个参数,属性id

    b.env->GetStaticObjectField(test_cls, test_fid);

    第一个参数,jclass,第二个参数,属性id

  6. 生成jobject对象,通常都是从Java方法中传递过来,还有一种情况是调用java的构造方法来生成jobject对象。

    获取构造方法id,env->GetMethodID(test_cls, ““,”(Ljava/lang/String;)V”);

    第一个参数jclass,第二个参数构造方法名称(固定),第三个参数构造方法签名(返回类型固定void签名V)

    生成jobject,env->NewObject(test_cls, init_mid, jstr);

    第一个参数jclass,第二个参数构造方法id,后面的参数依次是Java中构造函数的参数。

上述35调用的jni函数名称中,CharBooleanByteIntLongShortFloatDoubleObjectVoid,可以相互替换,除了Void,其他类型的函数均有返回值。

1
2
3
4
5
6
7
8
9
10
11
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

参照在Jni头文件中的定义,Object类型的函数,返回值是jobject,可以根据实际情况转成以上类型。

Author: flueky
Link: http://example.com/202/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.