Java编程教程之 JNI(Java Native Interface) 访问对象与回调方法

目录

 

5.访问对象的变量和回调方法

5.1访问对象的实例变量

5.2、获取类的静态变量

5.3回调实例方法和静态方法

5.4回调重写超类的实例方法

6.创建对象和对象数组

6.1回调构造函数以在本机代码中创建新的Java对象

6.2对象数组

7.本地和全球参考

8.调试JNI程序


如果遇到返回值是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);
}

要访问对象的实例变量:

  1. 通过获取对该对象类的引用GetObjectClass()
  2. 通过GetFieldID()类引用获取要访问的实例变量的字段ID 。您需要提供变量名称及其字段描述符(或签名)。对于Java类,字段描述符的形式为“ L<fully-qualified-name>;”,其中dot由正斜杠(/)替换,例如,类描述符为String“ Ljava/lang/String;”。对于原语,使用"I"for int"B"for byte"S"for short"J"for long"F"for float"D"for double"C"for char"Z"forboolean。对于数组,包括一个前缀"[",例如“ [Ljava/lang/Object;”表示数组Object"[I"对于一个数组int
  3. 基于Field ID,通过GetObjectField()或Get <primitive-type> Field()函数检索实例变量。
  4. 要更新实例变量,请使用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);
}

从本机代码回调实例方法:

  1. 通过获取对该对象类的引用GetObjectClass()
  2. 从类引用中获取方法ID GetMethodID()。您需要提供方法名称和签名。签名的格式为“ ”。您可以通过实用程序(类文件反汇编程序)使用(打印签名)和(显示私有成员)列出Java程序的方法签名:(parameters ) return-typejavap-s-p
  3. 基于此方法ID,你可以调用Call<Primitive-type>Method()CallVoidMethod()或者CallObjectMethod(),当返回类型是,无效和分别。在参数列表之前附加参数(如果有)。对于非返回类型,该方法返回一个值。Primitive-type >Objectvoid
> 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.创建对象和对象数组

您可以构造jobjectjobjectArray在本机代码中,通过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()检索jclassjmethodID以及jfieldID本地函数内。应该获取一次并缓存以供后续使用的值,而不是执行重复调用,以消除开销。

JNI将jobject本机代码使用的对象引用(for )分为两类:本地引用和全局引用:

  1. 本地参考被本机方法内创建,并且一旦释放的方法退出。它在本机方法的持续时间内有效。您还可以使用JNI函数DeleteLocalRef()显式地使本地引用无效,以便可以在中间进行垃圾回收。对象作为本地引用传递给本机方法。jobjectJNI函数返回的所有Java对象()都是本地引用。
  2. 一个全球参考保持,直到它被明确地由程序员中解脱出来,通过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!

猜你喜欢

转载自blog.csdn.net/xinqingwuji/article/details/81101054