使用 C 的一个简单 JNI
编写 Java 层代码
// HelloJNI.java
public class HelloJNI {
static {
System.loadLibrary("hello");
}
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello();
}
}
- 我们通过
System.loadLibrary()
来加载一个名字叫 hello 的动态库。该动态库的文件名在 Win 系统上叫 hello.dll,在 Linux 系统上是 libhello.so,在 MacOS 上是 libhello.dylib。动态库需要被添加到 Java 的库索引目录下,才能被System.loadLibrary()
找到。可以在打包时通过 VM 参数-Djava.library.path=/path/to/lib
来添加动态库路径。
在 Java8 以前,
System.loadLibrary()
只支持加载动态库。但是在 Java8 及以后,支持连接静态库。
生成 C 层头文件
从 JDK8 开始,我们使用 javac -h
来编译出 .class 文件和生成 Java 文件中 native
的方法所对应的头文件:
javac -h . HelloJNI.java
生成出来的头文件 HelloJNI.h 如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
该工程第一次生成该文件时,可能会提示找不到 jni.h 文件,原因和解决方法可以看:JNI 解惑。我们可以看到头文件声明了一个 C 函数 JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv *, jobject);
。它的命名规则是 Java_{package_and_classname}_{function_name}(JNI_arguments)
。
- 参数
JNIEnv
代表了 JNI 环境,通过它可以访问所有的 JNI 函数。 - 参数
jobject
代表了调用该方法的上层 Java 实例。 -
extern "C"
则涉及到一个 C++ 的概念:Name Mangling
JNIEXPORT
&JNICALL
:这两个宏定义的原理需要查看:when-to-use-jniexport-and-jnicall-in-android-ndk。
JNIEXPORT
使native
函数在动态表中可见,这样 JNI 才能找得到。如果不可见的话,那么在运行时调用RegisterNatives
方法将会失败。JNICALL
在 Android 平台上是一个空定义,它主要是为了平台兼容性而定义的,在 Windows 平台上,这个宏被定义为__stdcall
编写 C 层代码 HelloJNI.c
// "HelloJNI.c"
#include <jni.h> // JNI header provided by JDK
#include <stdio.h> // C Standard IO Header
#include "HelloJNI.h" // Generated
// Implementation of the native method sayHello()
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
下面我们使用 clang 来编译 libhello.dylib。
clang -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -dynamiclib -o libhello.dylib HelloJNI.c
上面的编译命令使我们能够编译出 libhello.dylib 动态库。然后再编译、运行 HelloJNI.java 即可得到输出。
编译 java 程序:
javac HelloJNI.java
,运行:java HelloJNI
。可能还需要指定 java 库的搜索路径:java -Djava.library.path=. HelloJNI
。
之所以使用 clang 而不是 gcc,是因为在 NDK r11 之后,Android 默认使用 clang/clang++:Why did you deprecate GCC?。而 clang 和 clang++ 的主要区别就在于,clang 的默认编译选项是支持 C 语言的,而 clang++ 的默认编译选项是支持 C++ 的:What is the difference? clang++ | clang -std=c++11。他们的主要区别在于链接时,clang++ 会默认链接 c++ 标准库,其实用 clang 一样可以编译 C++ 代码,只需要加上 -lc++(for libc++) 或者 -lstdc++(for libstdc++):Difference-between-clang-and-clang
编写 C++ 层代码 HelloJNI.cpp
我们同样可以用 c++ 来写上面的 JNI 层代码:
#include <jni.h>
#include <iostream>
#include "HelloJNI.h"
using namespace std;
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
cout << "Hello World from C++!" << endl;
return;
}
使用 clang++ 编译:clang++ -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -dynamiclib -o libhello.dylib HelloJNI.cpp
或者用 clang 编译:
clang -x c++ -lc++ -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -dynamiclib -o libhello.dylib HelloJNI.cpp
JNI 基本知识
JNI 定义了一些自己的类型,它们与 java 的类型是一一映射的:
- 基本类型:
jint
,jbyte
,jshort
,jlong
,jfloat
,jdouble
,jchar
,jboolean
,它们对应了 8 种 java 的基本类型:int
,byte
,short
,long
,float
,double
,char
,boolean
- 引用类型:
jobject
对应了java.lang.Object
。还有jobject
的子类型:
a.jclass
->java.lang.Class
b.jstring
->java.lang.String
c.jthrowable
->java.lang.Throwable
d.jarray
对应 java 数组。它代表了 8 种基本类型数组 和 一个Object
数组:jintArray
,jbyteArray
,jshortArray
,jlongArray
,jfloatArray
,jdoubleArray
,jcharArray
,jbooleanArray
和jobjectArray
。
jarray
在jni.h
中的定义就能看出:
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 的 native
函数接受和返回都是上面的 JNI 类型,但是它所调用的 C/C++ 业务逻辑方法却是接受 C/C++ 的数据类型,因此 native
的函数本质上就是一个胶水层转换代码,工作就是把 java 层传下来的 JNI 类型转换为 C/C++ 类型后给业务代码调用。最后将业务代码返回的 C/C++ 类型转换为 JNI 类型再返回给 java 层。
我们上面提到的 JNI 基本类型都是可以直接被 C/C++ 使用的:
// jni_md.h
typedef int jint;
// jni.h
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
因此主要的难点就在于引用类型的转换。下面就看下一些引用类型的转换。
字符串 jstring 的转换
public class TestJNIString {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes), libmyjni.dylib (MacOS)
}
// Native method that receives a Java String and return a Java String
private native String sayHello(String msg);
public static void main(String args[]) {
String result = new TestJNIString().sayHello("Hello from Java");
System.out.println("In Java, the returned string is: " + result);
}
}
javac -h . TestJNIString.java
生成 java 字节码和 C 层头文件。
C 层的实现
#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
// 第一步,将 JNI String (jstring) 转换成 C-String (char*)
const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
if (NULL == inCStr) return NULL;
printf("In C, the received string is: %s\n", inCStr);
// 释放 JNI 资源
(*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);
// Prompt user for a C-string
char outCStr[128];
printf("Enter a String: ");
scanf("%s", outCStr);
// 将 C-String (char*) 转换成 JNI String (jstring)
return (*env)->NewStringUTF(env, outCStr);
}
JNI 支持 Unicode 和 UTF-8 两种编码的字符串。UTF-8 字符串更像是 C-String(char
数组),大部分时候我们在 CC/C++ 代码中使用支持 UTF-8 编码的 JNI 函数:
// 返回使用 utf-8 编码 string 后得到的字节数组指针
const char* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
// 提示 VM 回收 utf 资源
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
// 从一个 utf-8 编码的字节数组中,编码后返回 java.lang.String 对象
jstring NewStringUTF(JNIEnv *env, const char *bytes);
// 返回 utf-8 编码的字符串的长度
jsize GetStringUTFLength(JNIEnv *env, jstring string);
// 将 str 中从 start 开始的 length 个字符编码成 utf-8 字符串,并放进 buf 中
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
- 上面代码有一个需要注意的地方:当你使用完
GetStringUTFChars()
返回的字符数组时,一定要调用ReleaseStringUTFChars()
以使 JVM 能够回收资源。
基本类型数组的转换
public class TestJNIPrimitiveArray {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes), libmyjni.dylib (MacOS)
}
// Declare a native method sumAndAverage() that receives an int[] and
// return a double[2] array with [0] as sum and [1] as average
private native double[] sumAndAverage(int[] numbers);
// Test Driver
public static void main(String args[]) {
int[] numbers = {22, 33, 33};
double[] results = new TestJNIPrimitiveArray().sumAndAverage(numbers);
System.out.println("In Java, the sum is " + results[0]);
System.out.println("In Java, the average is " + results[1]);
}
}
c 层的实现
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage(JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
// 第一步:将传入进来的 JNI jintarray 转换成 C 的 jint[]
jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
if (NULL == inCArray) return NULL;
jsize length = (*env)->GetArrayLength(env, inJNIArray);
jint sum = 0;
int i;
for (i = 0; i < length; i++) {
sum += inCArray[i];
}
jdouble average = (jdouble)sum / length;
// 第二步:释放资源
(*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0);
jdouble outCArray[] = {sum, average};
// 将 C 的 jdouble[] 转换成 jdoublearray
jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);
if (NULL == outJNIArray) return NULL;
(*env)->SetDoubleArrayRegion(env, outJNIArray, 0, 2, outCArray);
return outJNIArray;
}
- 跟上面 字符串 jstring 的转换 一样有个需要注意的地方:使用
GetIntArrayElements()
分配出来的jint[]
资源,需要用ReleaseIntArrayElements()
回收。
还有其他基本类型数组的 JNI 函数,这里不再分析。
访问实例变量
public class TestJNIInstanceVariable {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Instance variables
private int number = 88;
private String message = "Hello from Java";
// Declare a native method that modifies the instance variables
private native void modifyInstanceVariable();
// Test Driver
public static void main(String args[]) {
TestJNIInstanceVariable test = new TestJNIInstanceVariable();
test.modifyInstanceVariable();
System.out.println("In Java, int is " + test.number);
System.out.println("In Java, String is " + test.message);
}
}
上面的代码将在 C 层改变实例的私有变量值。
C 层代码实现
#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"
JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable(JNIEnv *env, jobject thisObj) {
// 获取到实例 class 的引用
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
// 获取到属性 number 的 id
jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
if (NULL == fidNumber) return;
// 从实例 thisObj 中根据属性 id 获取到属性的具体值
jint number = (*env)->GetIntField(env, thisObj, fidNumber);
printf("In C, the int is %d\n", number);
// 将新的值设置给实例 thisObj 中 id 为 fidNumber 的属性(即使这个属性是 private 的)
number = 99;
(*env)->SetIntField(env, thisObj, fidNumber, number);
jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
if (NULL == fidMessage) return;
jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);
const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
if (NULL == cStr) return;
printf("In C, the string is %s\n", cStr);
(*env)->ReleaseStringUTFChars(env, message, cStr);
message = (*env)->NewStringUTF(env, "Hello from C");
if (NULL == message) return;
(*env)->SetObjectField(env, thisObj, fidMessage, message);
}
- 通过
GetObjectClass()
获取类的jclass
- 通过
GetFieldID()
获取需要访问的属性 id。这个方法需要传入一个属性的签名 或 描述符。
在 java 中,类的描述符格式为 "L<fully-qualified-name>;",需要将全限定名中的 '.' 换成 '/',如
String
-> "Ljava/lang/String;"。对于基本类型,"I" ->int
,"B" ->byte
,"S" ->short
,"J" ->long
,"F" ->float
,"D" ->double
,“C” ->char
,"Z" ->boolean
。对于数组,则加一个前缀 "[",如 "[Ljava/lang/Object" ->Object[]
;"[I" ->int[]
- 通过属性 id,调用
GetObjectField()
或者Get<primitive-type>Field()
方法获取该属性在当前实例中的值。 - 通过属性 id,调用
SetObjectField()
或者Set<primitive-type>Field()
方法重新设置该属性在当前实例中的值。
访问类静态变量
方法跟上一节中 访问实例变量 差不多,只不过访问方法变成了 GetStaticFieldID()
,Get|SetStaticObjectField()
,Get|SetStatic<Primitive-type>Field()
。
回调实例方法 和 实例静态方法
public class TestJNICallBackMethod {
static {
System.loadLibrary("myjni");
}
private native void nativeMethod();
// To be called back by the native code
private void callback() {
System.out.println("In Java");
}
private void callback(String message) {
System.out.println("In Java with " + message);
}
private double callbackAverage(int n1, int n2) {
return ((double)n1 + n2) / 2.0;
}
// Static method to be called back
private static String callbackStatic() {
return "From static Java method";
}
public static void main(String[] args) {
new TestJNICallBackMethod().nativeMethod();
}
}
上面代码的 C 层方法 nativeMethod()
中会回调 Java 层方法 callback()
,callback(String message)
,callbackAverage(int, int)
,callbackStatic()
。下面我们看下 C 层是怎么实现的。
C 层代码实现
#include <jni.h>
#include <stdio.h>
#include "TestJNICallBackMethod.h"
JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod(JNIEnv *env, jobject thisObj) {
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
// 获取 method id,需要提供方法签名
jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
if (NULL == midCallBack) return;
printf("In C, call back Java's callback()\n");
// 根据 method id 回调 java 层方法
(*env)->CallVoidMethod(env, thisObj, midCallBack);
// 获取 method id,需要提供方法签名
jmethodID midCallBackStr = (*env)->GetMethodID(env, thisClass, "callback", "(Ljava/lang/String;)V");
if (NULL == midCallBackStr) return;
printf("In C, call back Java's called(String)\n");
jstring message = (*env)->NewStringUTF(env, "Hello from C");
// 反射调用 java 层方法,提供 JNI 数据类型
(*env)->CallVoidMethod(env, thisObj, midCallBackStr, message);
jmethodID midCallBackAverage = (*env)->GetMethodID(env, thisClass, "callbackAverage", "(II)D");
if (NULL == midCallBackAverage) return;
jdouble average = (*env)->CallDoubleMethod(env, thisObj, midCallBackAverage, 2, 3);
printf("In C, the average is %f\n", average);
// 反射调用 static 方法并没有什么特殊的地方,只是 JNI 方法名不同而已
jmethodID midCallBackStatic = (*env)->GetStaticMethodID(env, thisClass, "callbackStatic", "()Ljava/lang/String;");
if (NULL == midCallBackStatic) return;
jstring resultJNIStr = (*env)->CallStaticObjectMethod(env, thisClass, midCallBackStatic);
const char *resultCStr = (*env)->GetStringUTFChars(env, resultJNIStr, NULL);
if (NULL == resultCStr) return;
printf("In C, the returned string is %s\n", resultCStr);
// 通过 GetStringUTFChars 获得的资源,一定要用 ReleaseStringUTFChars 回收
(*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
}
上面的调用跟访问类属性时差不多,获取 Method ID 变成了 GetMethodID()
,需要提供方法签名和方法名。
回调方法通过传入 Method ID 调用相应的 Call<Primitive-type>Method()
,CallVoidMethod()
或者 CallObjectMethod()
方法。
在 Native 层创建新的 Java 对象
public class TestJNIConstructor {
static {
System.loadLibrary("myjni");
}
// Native method that calls back the constructor and return the constructed object.
// Return an Integer object with the given int.
private native Integer getIntegerObject(int number);
public static void main(String args[]) {
TestJNIConstructor obj = new TestJNIConstructor();
System.out.println("In Java, the number is :" + obj.getIntegerObject(9999));
}
}
上面代码将在 Native 层创建 Java 的实例对象,并返回给 Java 层。我们看看 C 层代码。
C 层 实现创建 Java 层对象
#include <jni.h>
#include <stdio.h>
#include "TestJNIConstructor.h"
JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject(JNIEnv *env, jobject thisObj, jint number) {
// 先通过 findClass 找到我们要构建的对象的 Class
jclass cls = (*env)->FindClass(env, "java/lang/Integer");
// 获取我们要构建对象的构造函数
jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
if (NULL == midInit) return NULL;
// 调用构造函数获取对象实例
jobject newObj = (*env)->NewObject(env, cls, midInit, number);
// 调用我们新构造出来的对象
jmethodID midToString = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
if (NULL == midToString) return NULL;
jstring resultStr = (*env)->CallObjectMethod(env, newObj, midToString);
const char *resultCStr = (*env)->GetStringUTFChars(env, resultStr, NULL);
printf("In C: the number is %s\n", resultCStr);
return newObj;
}
从上面代码可以看出,在 C 层创建 Java 对象跟我们在 C 层调用函数是差不多的,只不过我们调用的是 Java 层类的构造函数,然后通过 NewObject()
这样的方法构造出实例对象。
还有其他类型的创建对象的方法:
jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
,jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
,jobject AllocObject(JNIEnv *env, jclass cls);
。视当时的条件来调用不同的方法。
对象数组的转换
import java.util.ArrayList;
public class TestJNIObjectArray {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Native method that receives an Integer[] and
// returns a Double[2] with [0] as sum and [1] as average
private native Double[] sumAndAverage(Integer[] numbers);
public static void main(String args[]) {
Integer[] numbers = {11, 22, 32}; // auto-box
Double[] results = new TestJNIObjectArray().sumAndAverage(numbers);
System.out.println("In Java, the sum is " + results[0]); // auto-unbox
System.out.println("In Java, the average is " + results[1]);
}
}
我们传给 Native 层一个 Integer[]
,在 Native 层计算完成后,向 Java 层返回一个大小为 2 的 Double[]
数组,第一个为 Integer[]
数组的和,第二个为平均值。
C 层代码实现
#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"
JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage(JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
// 由于对象数组中每个元素取出来都是 jobject,因此需要调用 Integer.intValue() 将其转换成 jint
jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
if (NULL == midIntValue) return NULL;
jsize length = (*env)->GetArrayLength(env, inJNIArray);
jint sum = 0;
int i;
// 遍历对象数组的每个元素,将其求和
for (i = 0; i < length; i++) {
// 获取数组中的元素
jobject objInteger = (*env)->GetObjectArrayElement(env, inJNIArray, i);
if (NULL == objInteger) return NULL;
jint value = (*env)->CallIntMethod(env, objInteger, midIntValue);
sum += value;
}
double average = (double)sum / length;
printf("In C, the sum is %d\n", sum);
printf("In C, the average is %f\n", average);
// 分配大小为 2 的 Double 数组
jclass classDouble = (*env)->FindClass(env, "java/lang/Double");
jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);
jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
if (NULL == midDoubleInit) return NULL;
// 将结果创建两个 Double 对象
jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
// 将创建的两个 Double 对象放进 Double 数组
(*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
(*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);
return outJNIArray;
}
从上面我们可以注意到,对象数组有自己的一套 JNI 函数:NewObjectArray()
,GetObjectArrayElement()
,SetObjectArrayElement()
。
本地和全局引用
JNI 将对象的引用类型分为两类:本地引用 和 全局引用:
-
本地引用是由 native 方法创建的,当方法退出时,它会自动被回收。它在方法块的生命周期内都是合法的。可以显示调用
DeleteLocalRef()
方法以使 JVM 可以立即回收本地引用的资源。我们通过 native 方法传进来的 Java 对象都是本地引用,所有 JNI 方法返回的对象(jobject
)也都是局部引用。 -
全局引用只有被程序显示调用
DeleteGlobalRef()
时,资源才会被回收。通过NewGlobalRef()
来创建创建一个全局引用。
public class TestJNIReference {
static {
System.loadLibrary("myjni");
}
// A native method that returns a java.lang.Integer with the given int.
private native Integer getIntegerObject(int number);
// Another native method that also returns a java.lang.Integer with the given int.
private native Integer anotherGetIntegerObject(int number);
public static void main(String args[]) {
TestJNIReference test = new TestJNIReference();
System.out.println(test.getIntegerObject(1));
System.out.println(test.getIntegerObject(2));
System.out.println(test.anotherGetIntegerObject(11));
System.out.println(test.anotherGetIntegerObject(12));
System.out.println(test.getIntegerObject(3));
System.out.println(test.anotherGetIntegerObject(13));
}
}
上面代码我们每一次调用 getIntegerObject()
或者 anotherGetIntegerObject()
时,C 层的代码都会希望保存上一次的 jclass
等信息。我们看看一种不规范的保存 JNI 资源的办法。
不正确地保存 JNI 资源
#include <jni.h>
#include <stdio.h>
#include "TestJNIReference.h"
// 像传统 C 代码一样,使用全局静态变量。但是由于 JNI 函数返回的是 JNI 本地引用,
// 因此不能简单地这样保存。在第二次访问 classInteger 时,就会报错,因为 classInteger 将会是一个非法值(但不是 NULL)
static jclass classInteger;
static jmethodID midIntegerInit;
jobject getInteger(JNIEnv *env, jobject thisObj, jint number) {
if (NULL == classInteger) {
printf("Find java.lang.Integer\n");
classInteger = (*env)->FindClass(env, "java/lang/Integer");
}
if (NULL == classInteger) return NULL;
if (NULL == midIntegerInit) {
printf("Get Method ID for java.lang.Integer's constructor\n");
midIntegerInit = (*env)->GetMethodID(env, classInteger, "<init>", "(I)V");
}
if (NULL == midIntegerInit) return NULL;
jobject newObj = (*env)->NewObject(env, classInteger, midIntegerInit, number);
printf("In C, constructed java.lang.Integer with number %d\n", number);
return newObj;
}
JNIEXPORT jobject JNICALL Java_TestJNIReference_getIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
return getInteger(env, thisObj, number);
}
JNIEXPORT jobject JNICALL Java_TestJNIReference_anotherGetIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
return getInteger(env, thisObj, number);
}
上面的代码尝试重复使用 JNI 函数返回的本地引用,因此报错。我们如果想要重复使用 jclass
,可以为其创建一个全局引用将其保存:
if (NULL == classInteger) {
printf("Find java.lang.Integer\n");
// classInteger = (*env)->FindClass(env, "java/lang/Integer");
jclass classIntegerLocal = (*env)->FindClass(env, "java/lang/Integer");
classInteger = (*env)->NewGlobalRef(env, classIntegerLocal);
(*env)->DeleteLocalRef(env, classIntegerLocal);
}
这里只能为 jclass
创建全局引用,因为 jmethodID
和 jfieldID
都不是 jobject
,所以不是引用类型。
网友评论