概述
在上一篇的文章中,我们学会了在 Java 层如何通过 JNI 来调用 C 层的代码,这一篇文章我们将看一下如何在 C 层控制 Java 层的代码,主要包含以下内容:
- 创建 Java 对象
- 访问类静态成员域
- 调用类的静态方法
- 访问 Java 对象的成员变量
- 访问 Java 对象的方法
功能描述
我们下面要实现一个 Java 层与 C 层相互调用的混编程序,调用流程如下图:
- 整个 demo 从调用
JniFuncMain
类中的 createJniObject()
方法开始。
Java_JniFuncMain_createJniObject()
函数可以通过 JNI 来创建 JniTest
对象、调用该对象的方法以及访问该对象的成员变量。
编写 Java 层代码
JniFuncMain.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class JniFuncMain { private static int sIntField = 1; static { System.loadLibrary("jnifunc"); } public static native JniTest createJniObject(); public static void main(String[] args) { System.out.println("[Java] 调用 createJniObject() 方法"); JniTest jniObj = createJniObject(); jniObj.callTest(); } }
|
JniTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class JniTest { private int mIntField; public JniTest(int num) { mIntField = num; System.out.println("[Java] 调用 JniTest 对象的构造方法,mIntField值为 " + mIntField); } public int callByNative(int num) { num += mIntField; System.out.println("[Java] 调用 JniTest 对象的 callByNative() 方法,参数值为 " + num); return num; } public void callTest() { System.out.println("[Java] 调用 JniTest 对象的 callTest() 方法,mIntField值为 " + mIntField); } }
|
生成 JniFuncMain.h 头文件
因为 native
关键字在 JniFuncMain
类中,这里使用前文提到的 javah
命令生成该类的头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <jni.h>
#ifndef _Included_JniFuncMain #define _Included_JniFuncMain #ifdef __cplusplus extern "C" { #endif
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject (JNIEnv *, jclass);
#ifdef __cplusplus } #endif #endif
|
观察上述的头文件,我们发现生成的函数原型的第二个参数类型为 jclass
而不是 jsobject
。这是因为我们在 Java
中将这个方法声明为 static
静态方法,而静态方法是通过类而非对象来进行调用的。这个参数保存的是 JniFuncMain
类的引用而非类对象的引用。
实现函数原型 jnifunc.c
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
| #include "JniFuncMain.h" #include <stdio.h>
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject (JNIEnv *env, jclass clazz) { jclass targetClass; jmethodID mid; jobject newObject; jstring helloStr; jfieldID fid; jint sIntField; jint result;
fid = (*env)->GetStaticFieldID(env, clazz, "sIntField", "I"); sIntField = (*env)->GetStaticIntField(env, clazz, fid); printf("[C] 访问 JniFuncMain 类 的 sIntField 值为 %d\n", sIntField);
targetClass = (*env)->FindClass(env, "JniTest");
mid = (*env)->GetMethodID(env, targetClass, "<init>", "(I)V");
printf("[C] 创建 JniTest 对象"); newObject = (*env)->NewObject(env, targetClass, mid, 2);
mid = (*env)->GetMethodID(env, targetClass, "callByNative", "(I)I"); result = (*env)->CallIntMethod(env, newObject, mid, 1);
fid = (*env)->GetFieldID(env, targetClass, "mIntField", "I"); (*env)->SetIntField(env, newObject, fid, result); printf("[C] 设置 JniTest 对象的 mIntField,值为 %d\n", result);
return newObject; }
|
接下来生成动态链接库,如下:
1
| gcc jnifunc.c -I /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/include -I /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/include/darwin -shared -o libjnifunc.jnilib
|
可以从 C 代码观察到创建一个 Java 对象的顺序如下:
- 查找指定的类,并将查找到的类引用赋值给 jcalss 类型的变量(
FindClass()
方法)。
- 查找 Java 类构造方法的 ID 值(
GetMethodID()
方法,参数类型为 jmethodID
,方法名为 <init>
)。
- 生成 Java 类对象。
关于查找方法的补充说明:
如果是静态方法,则使用 GetStaticMethodID()
,方法的签名需要使用 javap
来获取。
局部引用与全局引用
在 JNI 本地函数中,由 FindClass()、GetObjectClass()
等 JNI 函数返回的 jclass
、 jobject
等引用都是局部引用,此类引用的作用域只在 JNI 本地函数中。一旦 JNI 本地函数返回后,其内部引用就会失效。
如果想让该引用转换成全局引用,可以使用 NewGlobalRef()
方法,之后将返回值存放在全局变量中,以便于在整个 C 代码中使用。如有要销毁该全局引用,请调用 DeleteGlobalRef()
方法。
Android 源码 中的 JNI
如果有兴趣了解 Android 底层是如何使用 JNI 的,可以查看下列源码:
1 2 3
| frameworks\base\core\jni frameworks\base\services\jni frameworks\base\media\jni
|