1、JNI 基础
JNI 中定义了一下类型来对应到相应的 Java 的数据类型:
1. Java 基本数据类型: jint
,jbyte
,jshort
,jlong
,jfloat
,jdouble
,jchar
,jboolean
分别对应 Java 中的int
,byte
,short
,long
,float
,double
,char
和boolean
。
2. Java 引用类型:
jobject
对应java.lang.object
。同时也定义了下列子类型:
jclass
对应java.lang.Class
jstring
对应java.lang.String
jthrowable
对应java.lang.Throwable
jarray
对应 Java 中的数组。Java 中的数组由 8 种基本数据类型和一个Object
类型派生二来,所以 JNI 中也存在jintArray
,jbyteArray
,jshortArray
,jlongArray
,jfloatArray
,jdoubleArray
,jcharArray
,jbooleanArray
和jobjectArray
native 函数接收和返回上述的 JNI 类型数据。如果 native 函数需要操作它自己的数据类型(如 c 语言中的 int, char *),那么就需要在 JNI 类型和本地类型之间做相应的转换。
简而言之,native 函数的编写流程大致为:
- 通过 Java 程序接收 JNI 类型的参数
- 将接收的 JNI 类型转换成本地类型
- 完成相应的操作
- 创建一个需要返回的 JNI 类型的对象,然后将返回的数据 copy 到要返回的对象中
- 返回
从上述流程可以看出,编写 JNI 程序主要的挑战在于数据类型之间的转换,然而 JNI 中提供了很多转换函数来帮助我们完成相应的操作。
JNI 是一个 c 语言的接口,c 语言并不支持 OOP 的特性(严格的说,OOP 是一种理念,这里只是从语言本身来说 c 语言不支持面向对象,实际上用 c 语言也可以写出面向对象风格的程序!),所以他们之间并不是真的通过对象来传递。
2、在 Java 程序和 native 程序之间传递参数
2.1 传递基本类型
传递 Java 中的基本数据类型是非常简单的。一个jxxx
类型被定义在 native 环境中,直接对应着 Java 中xxx
基本类型。
1public class TestAdd {
2 static {
3 System.loadLibrary("myadd");
4 }
5
6 private native double add(int a, int b);
7
8 public static void main(String[] args) {
9 System.out.println("In Java, the summary is " + new TestAdd().add(3, 2));
10 }
11}
编译 Java 并生成 C/C++头文件
实现 C 代码
1#include <stdio.h>
2#include <jni.h>
3#include "TestAdd.h"
4
5JNIEXPORT jdouble JNICALL Java_TestAdd_add (JNIEnv *env, jobject this, jint a, jint b) {
6 jint result;
7 printf("In c, the numbers are %d and %d\n", a, b);
8 result = (jint)(a + b);
9 return result;
10}
将 C 代码编译成动态链接库
1gcc -fPIC -shared -I /opt/app/java/include/ -I /opt/app/java/include/linux/ TestAdd.c -o libmyadd.so
运行 Java 程序
1java -Djava.library.path=. TestAdd
2
3====output====
4ubuntu@vm-911:~/workspace/java/jni/demo2$ java -Djava.library.path=. TestAdd
5In c, the numbers are 3 and 2
6In Java, the summary is 5.0
上面的例子是传递整型类型的情况,比较简单,不需要做转换处理,那么下面我们来看看传递字符串类型的情况。
2.2 传递 String
由于编译方式几乎每次都一样,所以接下来的例子只呈现代码,不再描述具体的编译操作,如果有问题请阅读 Java Native Interface(一)。
1public class TestJNIString {
2 static {
3 System.loadLibrary("myjnistring");
4 }
5
6 private native String say(String msg);
7
8 public static void main(String[] args) {
9 String result = new TestJNIString().say("hello liubang");
10 System.out.println("In Java, the result is " + result);
11 }
12}
实现 C 逻辑
1#include <stdio.h>
2#include <jni.h>
3#include "TestJNIString.h"
4
5JNIEXPORT jstring JNICALL Java_TestJNIString_say(JNIEnv *env, jobject this, jstring msg) {
6 const char *cString = (*env)->GetStringUTFChars(env, msg, NULL);
7 if (NULL == cString) {
8 return NULL;
9 }
10
11 printf("In c, the received string is %s\n", cString);
12 (*env)->ReleaseStringUTFChars(env, msg, cString);
13
14 char outPut[128];
15 printf("Enter a String: ");
16 scanf("%s", outPut);
17
18 return (*env)->NewStringUTF(env, outPut);
19}
由于上述代码太简单,所以这里不做具体的解释,关于相关函数的原型,可以在jni.h
头文件中找到定义。
测试结果如下:
1ubuntu@vm-911:~/workspace/java/jni/demo2$ java -Djava.library.path=. TestJNIString
2In c, the received string is hello liubang
3Enter a String: liubang
4In Java, the result is liubang
2.3 传递数组
TestJNIArray.java
1public class TestJNIArray {
2 static {
3 System.loadLibrary("myjniarray");
4 }
5
6 private native double[] avg(int[] numbers);
7
8 public static void main(String[] args) {
9 int[] numbers = {12, 25, 8};
10 double[] results = new TestJNIArray().avg(numbers);
11 System.out.println("In Java the sum is " + results[0]);
12 System.out.println("In java the avg is " + results[1]);
13 }
14}
TestJNIArray.c
1#include <jni.h>
2#include "TestJNIArray.h"
3
4JNIEXPORT jdoubleArray JNICALL Java_TestJNIArray_avg(JNIEnv *env, jobject this, jintArray jniarray) {
5 jint *cArray = (*env)->GetIntArrayElements(env, jniarray, NULL);
6 if (NULL == cArray)
7 return NULL;
8
9 jsize length = (*env)->GetArrayLength(env, jniarray);
10 jint sum = 0;
11 int i;
12 for (i = 0; i < length; i++) {
13 sum += cArray[i];
14 }
15 jdouble avg = (jdouble)sum / length;
16 (*env)->ReleaseIntArrayElements(env, jniarray, cArray, 0);
17
18 jdouble output[] = {sum, avg};
19 jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);
20 if (NULL == outJNIArray)
21 return NULL;
22
23 (*env)->SetDoubleArrayRegion(env, outJNIArray, 0, 2, output);
24 return outJNIArray;
25}
运行结果为:
1ubuntu@vm-911:~/workspace/java/jni/demo2$ java -Djava.library.path=. TestJNIArray
2In Java the sum is 45.0
3In java the avg is 15.0
3、访问对象的成员变量和回调函数
3.1 访问成员变量
TestObjVariable.java
1public class TestObjVariable {
2 static {
3 System.loadLibrary("libmytestobjvariable");
4 }
5
6 private String name = "liubang";
7 private int age = 24;
8
9 private native void setNameAndAge();
10
11 public static void main(String[] args) {
12 TestObjVariable test = new TestObjVariable();
13 test.setNameAndAge();
14 System.out.println("My name is " + test.name + " and I am " + test.age + " years old");
15 }
16}
C 语言中的实现:
TestObjVariable.c
1#include <stdio.h>
2#include <jni.h>
3#include "TestObjVariable.h"
4
5JNIEXPORT void JNICALL Java_TestObjVariable_setNameAndAge(JNIEnv *env, jobject obj) {
6 //获取当前对象的Class
7 jclass thisClass = (*env)->GetObjectClass(env, obj);
8
9 jfieldID fidAge = (*env)->GetFieldID(env, thisClass, "age", "I");
10 if (NULL == fidAge) {
11 return;
12 }
13
14 jint age = (*env)->GetIntField(env, obj, fidAge);
15 printf("In C, the age is %d\n", age);
16 age = 22;
17 (*env)->SetIntField(env, obj, fidAge, age);
18
19 jfieldID fidName = (*env)->GetFieldID(env, thisClass, "name", "Ljava/lang/String;");
20 if (NULL == fidName) {
21 return;
22 }
23
24 jstring name = (*env)->GetObjectField(env, obj, fidName);
25 const char *str = (*env)->GetStringUTFChars(env, name, NULL);
26 if (NULL == str) {
27 return;
28 }
29
30 printf("In C, the name is %s\n", str);
31 (*env)->ReleaseStringUTFChars(env, name, str);
32
33 name = (*env)->NewStringUTF(env, "钟灵");
34 if (NULL == name) {
35 return;
36 }
37 (*env)->SetObjectField(env, obj, fidName, name);
38}
运行结果为:
1ubuntu@vm-911:~/workspace/java/jni/demo2$ java -Djava.library.path=. TestObjVariable
2In C, the age is 24
3In C, the name is liubang
4My name is 钟灵 and I am 22 years old
可能在之前的例子中你会觉得平淡无奇,那么写到这里是不是发现有点意思了。
首先通过GetObjectClass
函数来获取当前对象的类,然后通过GetFieldID
函数来获取成员变量的 ID,这里你需要提供字段的名字和字段类型的描述符。对于一个 Java 类类型的描述符,通常用"L
基于字段 ID,通过GetObjectField
函数或者Get<primitive-type>Field
函数来获取实例对象的成员变量。
更新实例对象的成员变量,则调用SetObjectField
或者Set<primitive-type>Field
函数。
3.2 访问静态成员
访问静态成员同访问成员变量很相似,不同的是使用的 JNI 函数为GetStaticFieldID
, Get|SetStaticObjectField
, Get|SetStatic<Primitive-type>Field
。
TestObjStaticVariable.java
1public class TestObjStaticVariable {
2 static {
3 System.loadLibrary("libtestobjstaticvariable");
4 }
5
6 private static int age = 24;
7 private static String name = "liubang";
8
9 private native void setNameAndAge();
10
11 public static void main(String[] args) {
12 TestObjStaticVariable test = new TestObjStaticVariable();
13 test.setNameAndAge();
14 System.out.println("In java, my name is " + name + ", I am " + age + " years old");
15 }
16}
TestObjStaticVariable.c
1#include <stdio.h>
2#include <jni.h>
3#include "TestObjStaticVariable.h"
4
5JNIEXPORT void JNICALL Java_TestObjStaticVariable_setNameAndAge(JNIEnv *env, jobject obj) {
6 jclass class = (*env)->GetObjectClass(env, obj);
7
8 jfieldID fidAge = (*env)->GetStaticFieldID(env, class, "age", "I");
9 if (NULL == fidAge) {
10 return;
11 }
12
13 jint age = (*env)->GetStaticIntField(env, obj, fidAge);
14 printf("In C, the age is %d\n", age);
15
16 age = 22;
17 (*env)->SetStaticIntField(env, class, fidAge, age);
18}
3.3 调用实例方法和静态方法
TestInstanceMethod.java
1public class TestInstanceMethod {
2 static {
3 System.loadLibrary("testinstance");
4 }
5
6 private native void foo();
7
8 private void bar() {
9 System.out.println("hello liubang");
10 }
11
12 private void bar(String message) {
13 System.out.println("hello " + message);
14 }
15
16 private double avg(int a, int b) {
17 return ((double)a + b)/2.0;
18 }
19
20 private static String s() {
21 return "hello zhongling";
22 }
23
24 public static void main(String[] args) {
25 new TestInstanceMethod().foo();
26 }
27}
TestInstanceMethod.c
1#include <stdio.h>
2#include <jni.h>
3#include "TestInstanceMethod.h"
4
5JNIEXPORT void JNICALL Java_TestInstanceMethod_foo(JNIEnv *env, jobject obj) {
6
7 jclass thisClass = (*env)->GetObjectClass(env, obj);
8 jmethodID midBar = (*env)->GetMethodID(env, thisClass, "bar", "()V");
9 if (NULL == midBar) {
10 return;
11 }
12
13 printf("In C, call Java's bar()\n");
14 (*env)->CallVoidMethod(env, obj, midBar);
15
16 jmethodID midBarStr = (*env)->GetMethodID(env, thisClass, "bar", "(Ljava/lang/String;)V");
17 if (NULL == midBarStr) {
18 return;
19 }
20 printf("In C, call Java's s()\n");
21 jstring message = (*env)->NewStringUTF(env, "钟灵");
22 (*env)->CallVoidMethod(env, obj, midBarStr, message);
23
24 jmethodID midAvg = (*env)->GetMethodID(env, thisClass, "avg", "(II)D");
25 if (NULL == midAvg) {
26 return;
27 }
28 jdouble average = (*env)->CallDoubleMethod(env, obj, midAvg, 2, 5);
29 printf("In C, the average of 2 and 5 is %f\n", average);
30
31 jmethodID midStatic = (*env)->GetStaticMethodID(env, thisClass, "s", "()Ljava/lang/String;");
32 if (NULL == midStatic) {
33 return;
34 }
35
36 jstring res = (*env)->CallStaticObjectMethod(env, thisClass, midStatic);
37 const char *cStr = (*env)->GetStringUTFChars(env, res, NULL);
38 if (NULL == cStr) {
39 return;
40 }
41 printf("In C, the returned string is %s\n", cStr);
42 (*env)->ReleaseStringUTFChars(env, res, cStr);
43}
测试结果为:
1ubuntu@vm-911:~/workspace/java/jni/demo3$ java -Djava.library.path=. TestInstanceMethod
2In C, call Java's bar()
3hello liubang
4In C, call Java's s()
5hello 钟灵
6In C, the average of 2 and 5 is 3.500000
7In C, the returned string is hello zhongling
**注意:**也许细心的你已经发现了,在调用对象成员和静态成员(类成员)的时候,除了调用的函数中多了"static"之外,函数的参数也有所区别,对象成员中传入的是当前的对象实例,而静态成员传的则是当前的类。
上述代码非常简单,主要使用GetMethodID
函数来获取想要调用的成员方法的 methodID,这里需要解释的一点就是,对于函数的参数和返回值使用的是(参数)返回值
的形式来做描述符,这里的类型描述符跟前面讲到获取对象成员时候的规则是一样的,这里不再赘述。
4、创建对象和对象数组
4.1 使用构造方法创建对象
TestCreateObj.java
1public class TestCreateObj {
2 static {
3 System.loadLibrary("createobj");
4 }
5
6 private native Integer getIntegerObj(int n);
7
8 public static void main(String[] args) {
9 TestCreateObj t = new TestCreateObj();
10 System.out.println("In Java, the number is :" + t.getIntegerObj(1000));
11 }
12}
TestCreateObj.c
1#include <stdio.h>
2#include <jni.h>
3#include "TestCreateObj.h"
4
5JNIEXPORT jobject JNICALL Java_TestCreateObj_getIntegerObj(JNIEnv *env, jobject obj, jint number) {
6 jclass clazz = (*env)->FindClass(env, "java/lang/Integer");
7 jmethodID midCons = (*env)->GetMethodID(env, clazz, "<init>", "(I)V");
8
9 jobject intObj = (*env)->NewObject(env, clazz, midCons, number);
10 jmethodID midToString = (*env)->GetMethodID(env, clazz, "toString", "()Ljava/lang/String;");
11 if (NULL == midToString) {
12 return NULL;
13 }
14
15 jstring resStr = (*env)->CallObjectMethod(env, intObj, midToString);
16 const char *cStr = (*env)->GetStringUTFChars(env, resStr, NULL);
17 printf("In C: the number is %s\n", cStr);
18
19 return intObj;
20}
运行结果为:
1ubuntu@vm-911:~/workspace/java/jni/demo3$ java -Djava.library.path=. TestCreateObj
2In C: the number is 1000
3In Java, the number is :1000
4.2 创建对象数组
TestObjArr.java
1public class TestObjArr {
2 static {
3 System.loadLibrary("objarr");
4 }
5
6 private native Double[] sumAndAverage(Integer[] number);
7
8 public static void main(String args[]) {
9 Integer[] numbers = {11, 24, 88};
10 Double[] results = new TestObjArr().sumAndAverage(numbers);
11 System.out.println("In Java, the sum is " + results[0]);
12 System.out.println("In Java, the average is " + results[1]);
13 }
14}
TestObjArr.c
1#include <stdio.h>
2#include <jni.h>
3#include "TestObjArr.h"
4
5JNIEXPORT jobjectArray JNICALL Java_TestObjArr_sumAndAverage(JNIEnv *env, jobject obj, jobjectArray numbers) {
6 jclass classInt = (*env)->FindClass(env, "java/lang/Integer");
7 jmethodID midIntVal = (*env)->GetMethodID(env, classInt, "intValue", "()I");
8 if (NULL == midIntVal) {
9 return NULL;
10 }
11
12 jsize length = (*env)->GetArrayLength(env, numbers);
13 jint sum = 0;
14 int i;
15 for (i = 0; i < length; i++) {
16 jobject objInt = (*env)->GetObjectArrayElement(env, numbers, i);
17 if (NULL == objInt) {
18 return NULL;
19 }
20 jint value = (*env)->CallIntMethod(env, objInt, midIntVal);
21 sum += value;
22 }
23
24 double average = (double)sum / length;
25 printf("In C, the sum is %d\n", sum);
26 printf("In C, the average is %f\n", average);
27
28 jclass classDouble = (*env)->FindClass(env, "java/lang/Double");
29 jobjectArray outArr = (*env)->NewObjectArray(env, 2, classDouble, NULL);
30 jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
31 if (NULL == midDoubleInit) {
32 return NULL;
33 }
34
35 jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
36 jobject objAvg = (*env)->NewObject(env, classDouble, midDoubleInit, average);
37
38 (*env)->SetObjectArrayElement(env, outArr, 0, objSum);
39 (*env)->SetObjectArrayElement(env, outArr, 1, objAvg);
40
41 return outArr;
42}
测试结果为:
1ubuntu@vm-911:~/workspace/java/jni/demo3$ java -Djava.library.path=. TestObjArr
2In C, the sum is 123
3In C, the average is 41.000000
4In Java, the sum is 123.0
5In Java, the average is 41.0
注意: 在 JNI 中调用构造方法的时候,方法名称用的是<init>
评论