目录
如果遇到返回值是jobject的情况,需要的是jstring,直接强转就可以了。
jstring s = (jstring)jobject;
因为他们都是指针
5.访问对象的变量和回调方法
5.1访问对象的实例变量
JNI计划 - TestJNIInstanceVariable.java
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);
}
}
该类包含两个private
实例变量:一个int
名为的基元number
和一个String
被调用的基元message
。它还声明了一个本机方法,它可以修改实例变量的内容。
C实现 - TestJNIInstanceVariable.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"
JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
(JNIEnv *env, jobject thisObj) {
//获取对该对象类的引用
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
// int
//获取实例变量“number”的字段ID
jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
if (NULL == fidNumber) return;
//获取字段ID给出的int
jint number = (*env)->GetIntField(env, thisObj, fidNumber);
printf("In C, the int is %d\n", number);
//更改变量
number = 99;
(*env)->SetIntField(env, thisObj, fidNumber, number);
//获取实例变量“消息”的字段ID
jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
if (NULL == fidMessage) return;
// String
//获取给定字段ID的对象
jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);
//使用JNI字符串创建C字符串
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);
//使用JNI字符串创建C字符串
message = (*env)->NewStringUTF(env, "Hello from C");
if (NULL == message) return;
//修改实例变量
(*env)->SetObjectField(env, thisObj, fidMessage, message);
}
要访问对象的实例变量:
- 通过获取对该对象类的引用
GetObjectClass()
。 - 通过
GetFieldID()
类引用获取要访问的实例变量的字段ID 。您需要提供变量名称及其字段描述符(或签名)。对于Java类,字段描述符的形式为“L<fully-qualified-name>;
”,其中dot由正斜杠(/
)替换,例如,类描述符为String
“Ljava/lang/String;
”。对于原语,使用"I"
forint
,"B"
forbyte
,"S"
forshort
,"J"
forlong
,"F"
forfloat
,"D"
fordouble
,"C"
forchar
和"Z"
forboolean
。对于数组,包括一个前缀"["
,例如“[Ljava/lang/Object;
”表示数组Object
;"[I"
对于一个数组int
。 - 基于Field ID,通过GetObjectField()或Get <primitive-type> Field()函数检索实例变量。
- 要更新实例变量,请使用SetObjectField()或Set <primitive-type> Field()函数,提供字段ID。
用于访问实例变量的JNI函数是:
jclass GetObjectClass (JNIEnv *env, jobject obj);
// Returns the class of an object.
jfieldID GetFieldID (JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the field ID for an instance variable of a class.
NativeType Get< type >Field (JNIEnv *env, jobject obj, jfieldID fieldID);
void Set< type >Field (JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
// Get/Set the value of an instance variable of an object
// < type > includes each of the eight primitive types plus Object.
5.2、获取类的静态变量
访问类中的static变量类似于上面访问普通的实例变量,只是我们这里使用的函数是GetStaticFieldID(), Get|SetStaticObjectField(), Get|SetStatic_Primitive-type_Field()。
JNI 程序: TestJNIStaticVariable.java
public class TestJNIStaticVariable {
static {
System.loadLibrary("myjni"); // nyjni.dll (Windows) or libmyjni.so (Unixes)
}
// Static variables
private static double number = 55.66;
// Declare a native method that modifies the static variable
private native void modifyStaticVariable();
// Test Driver
public static void main(String args[]) {
TestJNIStaticVariable test = new TestJNIStaticVariable();
test.modifyStaticVariable();
System.out.println("In Java, the double is " + number);
}
}
C语言实现:C Implementation - TestJNIStaticVariable.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIStaticVariable.h"
JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
(JNIEnv *env, jobject thisObj) {
// Get a reference to this object's class
jclass cls = (*env)->GetObjectClass(env, thisObj);
// Read the int static variable and modify its value
jfieldID fidNumber = (*env)->GetStaticFieldID(env, cls, "number", "D");
if (NULL == fidNumber) return;
jdouble number = (*env)->GetStaticDoubleField(env, cls, fidNumber);
printf("In C, the double is %f\n", number);
number = 77.88;
(*env)->SetStaticDoubleField(env, cls, fidNumber, number);
}
JNI中用来访问类中的static变量的函数如下:
jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
//返回类的静态变量的字段ID。
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
//获取/设置类的静态变量的值。
// < type >包括八种基本类型和Object中的每一种。
5.3回调实例方法和静态方法
您可以从本机代码回调实例和静态方法。
JNI程序- TestJNICallBackMethod.java
public class TestJNICallBackMethod {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
//声明一个回调下面Java方法的本机方法
private native void nativeMethod();
//由本机代码回调
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;
}
//要回调的静态方法
private static String callbackStatic() {
return "From static Java method";
}
//测试驱动程序
public static void main(String args[]) {
new TestJNICallBackMethod().nativeMethod();
}
}
该类声明一个native
名为的方法nativeMethod()
,并调用它nativeMethod()
。该nativeMethod()
反过来,回调在这个类中定义的各种实例和静态方法。
C语言实现:TestJNICallBackMethod.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);
//获取方法“callback”的方法ID,该方法不接受arg并返回void
jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
if (NULL == midCallBack) return;
printf("In C, call back Java's callback()\n");
//回调方法(返回void),使用方法ID
(*env)->CallVoidMethod(env, thisObj, midCallBack);
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");
(*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);
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);
(*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
}
从本机代码回调实例方法:
- 通过获取对该对象类的引用
GetObjectClass()
。 - 从类引用中获取方法ID
GetMethodID()
。您需要提供方法名称和签名。签名的格式为“ ”。您可以通过实用程序(类文件反汇编程序)使用(打印签名)和(显示私有成员)列出Java程序的方法签名:(parameters ) return-type
javap
-s
-p
- 基于此方法ID,你可以调用
Call<Primitive-type>Method()
或CallVoidMethod()
或者CallObjectMethod()
,当返回类型是,无效和分别。在参数列表之前附加参数(如果有)。对于非返回类型,该方法返回一个值。< Primitive-type >
Object
void
> javap --help
> javap -s -p TestJNICallBackMethod
.......
private void callback();
Signature: ()V
private void callback(java.lang.String);
Signature: (Ljava/lang/String;)V
private double callbackAverage(int, int);
Signature: (II)D
private static java.lang.String callbackStatic();
Signature: ()Ljava/lang/String;
.......
要回调static
方法,使用GetMethodID(),
CallStatic<Primitive-type>Method()
,CallStaticVoidMethod()
或CallStaticObjectMethod()
。
用于回调实例方法和静态方法的JNI函数是:
jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
//返回类或接口的实例方法的方法ID。.
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
//调用对象的实例方法。
// <type>包括八个原语和Object中的每一个。
jmethodID GetStaticMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
//返回类或接口的实例方法的方法ID。
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
//调用对象的实例方法。
// <type>包括八个原语和Object中的每一个。
5.4回调重写超类的实例方法
JNI提供了一系列的形如 CallNonvirtual_Type_Method()之类的函数来调用父类实例的方法:
1. 首先获得Method ID,使用GetMethodID()
2. 基于上获得的Method ID,通过调用 CallNonvirtual_Type_Method()函数来调用相应的方法,并且在参数中给出object,父类和参数列表。
JNI中用来访问父类方法的函数:
NativeType CallNonvirtual< type >Method (JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, ...);
NativeType CallNonvirtual< type >MethodA (JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, const jvalue *args);
NativeType CallNonvirtual < type > MethodV(JNIEnv * env,jobject obj,jclass cls,jmethodID methodID,va_list args);
6.创建对象和对象数组
您可以构造jobject
和jobjectArray
在本机代码中,通过NewObject()
和newObjectArray()
函数,并将它们传递回Java程序。
6.1回调构造函数以在本机代码中创建新的Java对象
回调构造函数类似于回调方法。首先,通过传递“ <init>
”作为方法名称和“ V
”作为返回类型来获取构造函数的方法ID 。然后,您可以使用类似NewObject()
调用构造函数的方法来创建新的java对象。
JNI程序:TestJavaConstructor.java
public class TestJNIConstructor {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
//调用构造函数并返回构造对象的本机方法。
//返回给定int的Integer对象。
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
方法getIntegerObject()
。本机代码应根据给定的参数创建并返回一个Integer对象。
C实现 - TestJavaConstructor.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIConstructor.h"
JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
//获取java.lang.Integer的类引用
jclass cls = (*env)->FindClass(env, "java/lang/Integer");
//获取带有int的构造函数的方法ID
jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
if (NULL == midInit) return NULL;
//回调构造函数以使用int参数分配新实例
jobject newObj = (*env)->NewObject(env, cls, midInit, number);
//尝试在这个新创建的对象上运行toString()
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;
}
用于创建object(jobject
)的JNI函数是:
jclass FindClass(JNIEnv *env, const char *name);
jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
//构造一个新的Java对象。方法ID指示要调用的构造方法
jobject AllocObject(JNIEnv *env, jclass cls);
//在不调用对象的任何构造函数的情况下分配新的Java对象。
6.2对象数组
JNI程序:TestJNIObjectArray.java
import java.util.ArrayList;
public class TestJNIObjectArray {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
//接收Integer []的本机方法和
//返回Double [2],其中[0]为sum,[1]为平均值
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
,计算它们的总和和平均值,并作为数组返回Double
。请注意,对象数组是传入和传出本机方法的。
C实现 - TestJNIObjectArray.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"
JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
(JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
//获取java.lang.Integer的类引用
jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
//使用Integer.intValue()来检索int
jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
if (NULL == midIntValue) return NULL;
//获取数组中每个Integer对象的值
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);
//获取java.lang.Double的类引用
jclass classDouble = (*env)->FindClass(env, "java/lang/Double");
//分配一个2个java.lang.Double的jobjectArray
jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);
//通过调用构造函数构造2个Double对象
jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
if (NULL == midDoubleInit) return NULL;
jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
// Set to the jobjectArray
(*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
(*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);
return outJNIArray;
}
与可以批量处理的原始数组不同,对于对象数组,您需要使用它Get|SetObjectArrayElement()
来处理每个元素。
用于创建和操作对象数组(jobjectArray
)的JNI函数是:
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
//在类elementClass中构造一个包含对象的新数组。
//所有元素最初都设置为initialElement。
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
//返回Object数组的元素。
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
//设置Object数组的元素。
7.本地和全球参考
管理参考对于编写有效的程序至关重要。例如,我们经常使用FindClass()
,GetMethodID()
,GetFieldID()
检索jclass
,jmethodID
以及jfieldID
本地函数内。应该获取一次并缓存以供后续使用的值,而不是执行重复调用,以消除开销。
JNI将jobject
本机代码使用的对象引用(for )分为两类:本地引用和全局引用:
- 甲本地参考被本机方法内创建,并且一旦释放的方法退出。它在本机方法的持续时间内有效。您还可以使用JNI函数
DeleteLocalRef()
显式地使本地引用无效,以便可以在中间进行垃圾回收。对象作为本地引用传递给本机方法。jobject
JNI函数返回的所有Java对象()都是本地引用。 - 一个全球参考保持,直到它被明确地由程序员中解脱出来,通过
DeleteGlobalRef()
JNI功能。您可以通过JNI函数从本地引用创建新的全局引用NewGlobalRef()
。
public class TestJNIReference {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// 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));
}
}
上面的JNI程序声明了两个native函数,这两个都创建并且返回java.lang.Integer对象。在C代码实现中,我们需要获得java.lang.Integer的类引用,然后我们从中找到构造器的method ID,然后调用构造器。然而,我们希望,将我们获得的class引用和Method ID缓存起来,这样我们下次在使用的时候就不用再次去获取了。
以下C实现不起作用!
#include <jni.h>
#include <stdio.h>
#include "TestJNIReference.h"
// Global Reference to the Java class "java.lang.Integer"
static jclass classInteger;
static jmethodID midIntegerInit;
jobject getInteger(JNIEnv *env, jobject thisObj, jint number) {
// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
printf("Find java.lang.Integer\n");
classInteger = (*env)->FindClass(env, "java/lang/Integer");
}
if (NULL == classInteger) return NULL;
// Get the Method ID of the Integer's constructor if missing
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;
// Call back constructor to allocate a new instance, with an int argument
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);
}
在上面的程序中,我们调用FindClass()
查找类的引用java.lang.Integer
,并将其保存在全局静态变量中。尽管如此,在下一次调用中,此引用不再有效(而不是NULL)。这是因为FindClass()
返回本地引用,一旦方法退出就会失效。
为了解决这个问题,我们需要从返回的本地引用创建一个全局引用FindClass()
。然后我们可以释放本地参考。修订后的代码如下:
//如果缺少,请获取java.lang.Integer的类引用
if(NULL == classInteger){
printf(“Find java.lang.Integer \ n”);
// FindClass返回本地引用
jclass classIntegerLocal =(* env) - > FindClass(env,“java / lang / Integer”);
//从本地引用创建全局引用
classInteger =(* env) - > NewGlobalRef(env,classIntegerLocal);
//不再需要本地参考,免费!
(* env) - > DeleteLocalRef(env,classIntegerLocal);
}
请注意jmethodID
并且jfieldID
不是jobject
,并且不能创建全局引用。
8.调试JNI程序
just do it!