写在最前面
在上一篇博客中,讲解了JNI的运行方式以及Java中调用C函数的步骤,本篇博客将讲解在由C语言编写的JNI本地函数中如何控制Java端的代码,主要包括以下内容
创建Java对象
访问类静态成员域
调用类的静态方法
访问Java对象的成员变量
访问Java对象的方法
调用JNI函数的示例程序
在开始之前,我们先大致看一下示例程序的整体架构。整个示例程序由JniFuncMain类(包含本地方法声明)、JniTest对象、libjnifunc.jnilib(包含本地方法的具体实现)三部分组成。
整个示例程序从调用JniFuncMain类的createJniObject()本地方法开始,该方法经由JNI与libjnitest.jnilib中命名为Java_JniFuncMain_createJniObject()的C函数链接在一起。
Java层代码(JniFuncMain.java)
1. JniFuncMain类
1 public class JniFuncMain
2 {
3 private static int staticIntField = 300;
4
5 // 加载本地库
6 static { System.loadLibrary("jnifunc");}
7
8 // 本地方法声明
9 public static native JniTest createJniObject();
10
11 public static void main(String[] args)
12 {
13 // 从本地代码生成JniTest对象
14 System.out.println("[Java] createJniObject()调用本地方法");
15 JniTest jniObj = createJniObject();
16
17 // 调用JniTest对象的方法
18 jniObj.callTest();
19 }
20 }
2.JniTest类
1 class JniTest
2 {
3 private int intField;
4
5 // 构造方法
6 public JniTest(int num)
7 {
8 intField = num;
9 System.out.println("[java] 调用JniTest对象的构造方法:intField = " + intField);
10 }
11
12 // 此方法由JNI本地函数调用
13 public int callByNative(int num)
14 {
15 System.out.println("[Java] JniTest对象的callByNative(" + num + ")调用");
16 return num;
17 }
18
19 public void callTest()
20 {
21 System.out.println("[Java] JniTest对象的callTest()方法调用:intField = " + intField);
22 }
23 }
分析JNI本地函数代码
1.JniFuncMain.h头文件
在JniFuncMain.java代码中,仅仅在本地方法createJniObject()进行了声明,接下来,我们用C语言实现它。首先,使用javah命令,生成本地方法的函数原型,命令如下(此命令之前要先编译JniFuncMain.java文件)。
javac JniFuncMain.java
javah JniFuncMain
执行以上命令后,生成如下JniFuncMain.h头文件
1 /* DO NOT EDIT THIS FILE - it is machine generated */
2 #include <jni.h>
3 /* Header for class JniFuncMain */
4
5 #ifndef _Included_JniFuncMain
6 #define _Included_JniFuncMain
7 #ifdef __cplusplus
8 extern "C" {
9 #endif
10 /*
11 * Class: JniFuncMain
12 * Method: createJniObject
13 * Signature: ()LJniTest;
14 */
15 JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
16 (JNIEnv *, jclass);
17
18 #ifdef __cplusplus
19 }
20 #endif
21 #endif
仔细查看上面的代码,可以看到createJniObject()本地方法对应的JNI本地函数原型,形式如下
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject (JNIEnv *, jclass);
这个函数原型与我们的Java类JniFuncMain的createJniObject()本地方法相对应。在上一篇博客中,我们知道使用javah命令生成的JNI本地函数原型的第二个参数默认类型为jobject。但观察上面的代码,会发现它的第二个参数类型为jclass,而不是jobject。原因是什么?若想弄清楚这个问题,首先要理解函数原型第二个参数的含义。在上一篇博客中,我们理解到,jobject类型的变量用来保存调用本地方法的对象的引用。
那么我们再回过头看看我们本地方法的声明
public static native JniTest createJniObject();
这个本地方法被声明为static(静态)方法。在Java中,调用静态方法时,可以不用创建对象,所以通过所在的类直接调用即可,即本地静态方法通过类而非对象进行调用,因此函数原型的第二个参数类型为jclass类型。
2.JniFuncMain.c文件
1 #include "JniFuncMain.h"
2 #include <stdio.h>
3
4 JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env, jclass clazz)
5 {
6 jclass targetClass;
7 jmethodID mid;
8 jobject newObject;
9 jstring helloStr;
10 jfieldID fid;
11 jint staticIntField;
12 jint result;
13
14 // 获取JniFuncMain类的staticIntField变量值
15 fid = (*env)->GetStaticFieldID(env,clazz,"staticIntField","I");
16 staticIntField = (*env)->GetStaticIntField(env,clazz,fid);
17 printf("[CPP]获取JniFuncMain类的staticIntField值\n");
18 printf(" JniFuncMain.staticIntField = %d\n", staticIntField);
19
20 // 查找生成对象的类
21 targetClass = (*env)->FindClass(env,"JniTest");
22
23 // 查找构造方法
24 mid = (*env)->GetMethodID(env,targetClass,"<init>","(I)V");
25
26 // 生成JniTest对象(返回对象的引用)
27 printf("[CPP] JniTest对象生成\n");
28 newObject = (*env)->NewObject(env,targetClass,mid,100);
29
30 // 调用对象的方法
31 mid = (*env)->GetMethodID(env,targetClass, "callByNative","(I)I");
32 result = (*env)->CallIntMethod(env,newObject,mid,200);
33
34 // 设置JniObject对象的intField值
35 fid = (*env)->GetFieldID(env,targetClass,"intField","I");
36 printf("[CPP]设置JniTest对象的intField值为200\n");
37 (*env)->SetIntField(env,newObject,fid,result);
38
39 // 返回对象的引用
40 return newObject;
41 }
编译
执行如下命令
gcc -dynamiclib -I /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/include/ JniFuncMain.c -o libjnifunc.jnilib
得到C链接库,
然后执行java JniFuncMain,得到如下结果
[Java] createJniObject()调用本地方法
[CPP]获取JniFuncMain类的staticIntField值
JniFuncMain.staticIntField = 300
[CPP] JniTest对象生成
[java] 调用JniTest对象的构造方法:intField = 100
[Java] JniTest对象的callByNative(200)调用
[CPP]设置JniTest对象的intField值为200
[Java] JniTest对象的callTest()方法调用:intField = 200
达到我们的预期。