[Android -- JNI and NDK] Pass parameters and return values between Java and C/C++

insert image description here

This article mainly introduces the data transfer of JNI, that is, how Java transfers objects to C++; and how C++ encapsulates data into objects required by Java.

1. Basic data types

It is very simple and direct to transfer the basic types of java. A type like jxxx has been defined in the local system, for example: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar 和 jbooleancorresponding to int, byte, short, long, float, double, char 和 booleanthe basic types of java respectively.

Java JNI program: TestJNIPrimitive.java

public class TestJNIPrimitive {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Declare a native method average() that receives two ints and return a double containing the average
   private native double average(int n1, int n2);

   // Test Driver
   public static void main(String args[]) {
    
    
      System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
   }
}

This JNI program loads myjni.dll(windows)the library or libmyjni.so(类UNIX)libraries. And declare a nativemethod , this method accepts two inttypes of parameters, and returns a return value of doubletype , this value is intthe average of the two types of numbers. mianThe method calls averagethe function .

Next, we compile the above java code into TestJNIPrimitive.class, and then generate

C/C++ header file TestJNIPrimitive.h:

> javac TestJNIPrimitive.java
> javah TestJNIPrimitive       // Output is TestJNIPrimitive.h

C implementation: The TestJNIPrimitive.c
header TestJNIPrimitive.hfile contains a function declaration:

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *, jobject, jint, jint);

It can be seen that here jintand jdoublerepresent respectively intand double.
jni.h( windowsAbove win32/jni_mh.h) The header file contains the definition of these data types, and one more jsizedefinition:

// In "win\jni_mh.h" - machine header which is machine dependent
typedef long            jint;
typedef __int64         jlong;
typedef signed char     jbyte;

// In "jni.h"
typedef unsigned char   jboolean;
typedef unsigned short  jchar;
typedef short           jshort;
typedef float           jfloat;
typedef double          jdouble;
typedef jint            jsize;

Interestingly, jintit corresponds to the C longtype (at least 32bit), not the C inttype (at least 16bit). Therefore, it is important to use jintinstead of in C code . intAt the same time, CygWinthe __int64 type is not supported.
The implementation of TestJNIPrimitive.c is as follows :

#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitive.h"

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
         (JNIEnv *env, jobject thisObj, jint n1, jint n2) {
    
    
  jdouble result;
  printf("In C, the numbers are %d and %d\n", n1, n2);
  result = ((jdouble)n1 + n2) / 2.0;
  // jint is mapped to int, jdouble is mapped to double
  return result;
}

Then, we compile the code into a shared library:

// MinGW GCC under Windows
> set JAVA_HOME={jdk-installed-directory}
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o myjni.dll TestJNIPrimitive.c

Finally, we run this java code:

java TestJNIPrimitive

C++ implements the TestJNIPrimitive.cpp code as follows:

#include <jni.h>
#include <iostream>
#include "TestJNIPrimitive.h"
using namespace std;

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject obj, jint n1, jint n2) {
    
    
   jdouble result;
   cout << "In C++, the numbers are " << n1 << " and " << n2 << endl;
   result = ((jdouble)n1 + n2) / 2.0;
   // jint is mapped to int, jdouble is mapped to double
   return result;
}

Use g++ to compile the above code:

// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o myjni.dll TestJNIPrimitive.cpp

2. Pass the string

Java JNI program:TestJNIString.java

public class TestJNIString {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
   // 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);
   }
}

The above code declares a nativefunction sayHellothat accepts a java Stringand returns a Java string, mainthe method calls sayHellothe function .

Then, we compile the above code and generate C/C++ header files:

> javac TestJNIString.java
> javah TestJNIString

C code implementation:
The header file above TestJNIString.c TestJNIString.hdeclares such a function:

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);

JNI defines jstringtypes that correspond to java's Stringtypes. The last parameter in the above statement jstringis Stringthe parameter from the Java code, and the return value is also a jstringtype .

Passing a string is more complicated than passing a primitive type, because java's Stringis an object, while C's stringis an array NULLat the end . charTherefore, we need to convert the Java Stringobject into a C string representation: char *.

As we mentioned earlier, the JNI environment pointer JNIEnv *has defined a very rich interface function for us to handle data conversion:

  1. Call const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)to convert JNI's jstringto C'schar *
  2. Call jstring NewStringUTF(JNIEnv*, char*)to convert C's char *to JNI'sjstring

Therefore, the basic process of our C program is as follows:

  1. Use GetStringUTFChars()the function to jstringconvert tochar *
  2. Then perform the required data processing
  3. Use NewStringUTF()the function to char *convert to jstring, and return
#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
    
    
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
   if (NULL == inCSt) return NULL;

   // Step 2: Perform its intended operations
   printf("In C, the received string is: %s\n", inCStr);
   (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);  // release resources

   // Prompt user for a C-string
   char outCStr[128];
   printf("Enter a String: ");
   scanf("%s", outCStr);    // not more than 127 characters

   // Step 3: Convert the C-string (char*) into JNI String (jstring) and return
   return (*env)->NewStringUTF(env, outCStr);
}

Compile the above code into a shared library:

// MinGW GCC under Windows
> gcc -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.c

Finally, run the code:

> java TestJNIString
In C, the received string is: Hello from Java
Enter a String: test
In Java, the returned string is: test

The string conversion function in JNI
We have shown two functions above, now we will comprehensively sort out the functions provided by JNI. JNI supports Unicode (16bit characters) and UTF-8 (using 1~3 byte encoding) conversion. Generally speaking, we should use UTF-8 encoding in C/C++.
The JNI system provides the following functions for string processing (a total of two groups, UTF8 and Unicode):

// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
   // Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
   // Informs the VM that the native code no longer needs access to utf.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
   // Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
   // Returns the length in bytes of the modified UTF-8 representation of a string.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
   // Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding 
   // and place the result in the given buffer buf.

// Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
   // Returns a pointer to the array of Unicode characters
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
   // Informs the VM that the native code no longer needs access to chars.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
   // Constructs a new java.lang.String object from an array of Unicode characters.
jsize GetStringLength(JNIEnv *env, jstring string);
   // Returns the length (the count of Unicode characters) of a Java string.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
   // Copies len number of Unicode characters beginning at offset start to the given buffer buf

GetStringUTFChars()The function can be jstringconverted to char *, and this function will return NULLif the content allocation of the system fails. Therefore, it is good practice to check whether the return of this function is correct NULL. The third parameter is isCopythat this parameter is a in-outparameter, and what is passed in is a pointer, and the content of the pointer will be modified when the function ends. If the content is JNI_TRUEyes, then it means that the returned data is jstringa copy of the data, otherwise, if JNI_FALSEyes, it means that the returned string is directly pointing to that Stringobject instance. In this case, the native code should not arbitrarily modify stringthe content in the code, because the modification will modify the code in Java. The JNI system will try to ensure that the return is a direct reference, if not, then return a copy. Usually, we rarely care about modifying these , so we generally pass to parameters stringhere .NULLisCopy

It must be noted that when you no longer need the GetStringUTFCharsreturned string, you must remember to call ReleaseStringUTFChars()the function to release the memory resource! Otherwise there will be a memory leak! And the upper level java GCcan't be done either!

In addition, in GetStringUTFCharsand ReleaseStringUTFCharscannot block!
NewStringUTF()Functions are char *available from strings jstring.

C++ implementation: TestJNIString.cpp

#include <jni.h>
#include <iostream>
#include <string>
#include "TestJNIString.h"
using namespace std;

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
    
    
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
   if (NULL == inCStr) return NULL;

   // Step 2: Perform its intended operations
   cout << "In C++, the received string is: " << inCStr << endl;
   env->ReleaseStringUTFChars(inJNIStr, inCStr);  // release resources

   // Prompt user for a C++ string
   string outCppStr;
   cout << "Enter a String: ";
   cin >> outCppStr;

   // Step 3: Convert the C++ string to C-string, then to JNI String (jstring) and return
   return env->NewStringUTF(outCppStr.c_str());
}

Compile the above code with g++:

// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.cpp

It should be noted that in C++, stringthe function call syntax of local classes is different. In C++, we use env->to call instead of (env*)->. At the same time, this parameter is not needed in the C++ function JNIEnv*.

3. Pass an array of primitive types

JNI code: TestJNIPrimitiveArray.java

public class TestJNIPrimitiveArray {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // 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 language implementation: TestJNIPrimitiveArray.c
header file TestJNIPrimitiveArray.hcontains the following function declarations:

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_average (JNIEnv *, jobject, jintArray);

In Java, arrayrefers to a type, similar to a class. There are 9 types of java array, 8 basic types of arrays and an array of objects. JNI defines corresponding arrays for the basic types of java: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray, and there are also object-oriented ones jobjectArray.

Similarly, you need to convert between JNI array and Native array. The JNI system has provided us with a series of interface functions:

  1. Use jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)will jintarrayconvert to C'sjint[]
  2. Use jintArray NewIntArray(JNIEnv *env, jsize len)the function to allocate a lenbyte-sized space, and then use void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)the function jint[]to copy the data in it to jintArrayit.

There are a total of 8 pairs of functions similar to the above, corresponding to the 8 basic data types of java.
Therefore, native programs need to:

  1. Accept JNI array from java and convert to native array
  2. Perform required data manipulation
  3. Convert the data that needs to be returned into jnian array, and then return it.
    The following is the implementation of the C code TestJNIPrimitiveArray.c:
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
    
    
   // Step 1: Convert the incoming JNI jintarray to C's jint[]
   jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
   if (NULL == inCArray) return NULL;
   jsize length = (*env)->GetArrayLength(env, inJNIArray);

   // Step 2: Perform its intended operations
   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); // release resources

   jdouble outCArray[] = {
    
    sum, average};

   // Step 3: Convert the C's Native jdouble[] to JNI jdoublearray, and return
   jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);  // allocate
   if (NULL == outJNIArray) return NULL;
   (*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray);  // copy
   return outJNIArray;
}

The array function of the JNI basic type The array( ) function
of the JNI basic type is as follows:jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray 和 jbooleanArray

// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

Similarly get, releasethere cannot be an always block between the function and the function.

4. Access Java object variables and callback Java methods

Access the variable
JNI program of the Java object instance: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);
   }
}

This class contains two privateinstance variables, an int and a String object. Then we call the local function in main modifyInstanceVariableto modify these two variables.

C code implementation:TestJNIInstanceVariable.c

#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"

JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
          (JNIEnv *env, jobject thisObj) {
    
    
   // Get a reference to this object's class
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // int
   // Get the Field ID of the instance variables "number"
   jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
   if (NULL == fidNumber) return;

   // Get the int given the Field ID
   jint number = (*env)->GetIntField(env, thisObj, fidNumber);
   printf("In C, the int is %d\n", number);

   // Change the variable
   number = 99;
   (*env)->SetIntField(env, thisObj, fidNumber, number);

   // Get the Field ID of the instance variables "message"
   jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
   if (NULL == fidMessage) return;

   // String
   // Get the object given the Field ID
   jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);

   // Create a C-string with the JNI String
   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);

   // Create a new C-string and assign to the JNI string
   message = (*env)->NewStringUTF(env, "Hello from C");
   if (NULL == message) return;

   // modify the instance variables
   (*env)->SetObjectField(env, thisObj, fidMessage, message);
}

In order to access variables in an object, we need to:

  1. Call GetObjectClass()to obtain the class reference of the target object
  2. To access a variable obtained from the class reference obtained above Field ID, you need to provide the name of the variable, and the variable's descriptor (also known as the signature). For the java class, the descriptor is in this form: "Lfully-qualified-name;" (note that there is an English half-width semicolon at the end), where the package name dot is replaced with a slash (/), such as java's Stirng The class descriptor is "Ljava/lang/String;". For basic types, I stands for int, B stands for byte, S stands for short, J stands for long, F stands for float, D stands for double, C stands for char, and Z stands for boolean. For array, use left square brackets "[" to indicate, such as "[Ljava/lang/Object;" means Object array, "[I" means int type array.
  3. Based on the above obtained Field ID, use GetObjectField()or Get_primitive-type_Field()function to parse out the data we want
  4. Use SetObjectField()the or Set_primitive-type_Field()function to modify variables

The functions used to access instance variables in JNI are:

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.

Accessing static variables in a class
Accessing static variables in a class is similar to accessing ordinary instance variables above, except that the functions we use here are GetStaticFieldID(), Get|SetStaticObjectField(), Get|SetStatic_Primitive-type_Field().
JNI program: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 language implementation: 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);
}

staticThe functions used to access variables in the class in JNI are as follows:

jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
  // Returns the field ID for a static variable of a class.

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
  // Get/Set the value of a static variable of a class.
  // <type> includes each of the eight primitive types plus Object.

Calling back ordinary and static methods of instances
You can nativecall back ordinary or static methods in java in your code. The following is an example:
JNI program:TestJNICallBackMethod.java

public class TestJNICallBackMethod {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Declare a native method that calls back the Java methods below
   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";
   }

   // Test Driver 
   public static void main(String args[]) {
    
    
      new TestJNICallBackMethod().nativeMethod();
   }
}

A native function is declared in this class nativeMethod(), and this function is called in the main method. nativeMethod()This function calls back various methods defined in this class.
C language implementation:TestJNICallBackMethod.c

#include <jni.h>
#include <stdio.h>
#include "TestJNICallBackMethod.h"

JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod
          (JNIEnv *env, jobject thisObj) {
    
    

   // Get a class reference for this object
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // Get the Method ID for method "callback", which takes no arg and return void
   jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
   if (NULL == midCallBack) return;
   printf("In C, call back Java's callback()\n");
   // Call back the method (which returns void), baed on the Method 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);
}

To be able to call back methods on an instance, we need:

  1. GetObjectClass()Get the class object of this instance through the function
  2. From the class object obtained above, call GetMethodID()the function to obtain the Method ID, which represents the abstraction of a method in the instance. You need to provide the name and signature information of this method. The signature rules are similar to variables. The format of the signature is this: (parameters)return-type. If we really feel that the signature of jni is not easy to remember, we can use the tool javap provided by JDK to obtain the signatures of all methods in a certain class, use the -s option to print the signature, and -p to display private members :
> 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;
  .......

From the above output we can clearly see the signature of each method in the class.

  1. Based on the Method ID we obtained above, we can call _Primitive-type_Method()or CallVoidMethod()or CallObjectMethod()to call this method. If a method requires parameters, just follow the parameters.
  2. If you want to call a static method, use GetMethodID(), CallStatic_Primitive-type_Method(), CallStaticVoidMethod()or CallStaticObjectMethod().
    All functions used to call back instance and static methods in JNI (two types, common and static):
jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
   // Returns the method ID for an instance method of a class or interface.

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);
   // Invoke an instance method of the object.
   // The <type> includes each of the eight primitive and Object.

jmethodID GetStaticMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
   // Returns the method ID for an instance method of a class or interface.

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);
   // Invoke an instance method of the object.
   // The <type> includes each of the eight primitive and Object.

The parent class instance method overridden by the callback
JNI provides a series of functions CallNonvirtual_Type_Method()such to call the method of the parent class instance:

  1. First get the Method ID, useGetMethodID()
  2. Based on the Method ID obtained above, the corresponding method is invoked by calling CallNonvirtual_Type_Method()the function , and given in parameters object, parent class and parameter list.

Functions used in JNI to access parent class methods:

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);

To create Object and Object arrays you can construct and
in native code , by calling and functions , and then tell them to return to java code.jobjectjobjectarrayNewObject()newObjectArray()

Calling back a Java constructor to create a new java object
Calling back a constructor is similar to calling back other methods. First, initget the Method ID by using it as the method name and V as the return value, and then NewObject()build a java class object through the function.

JNI program:TestJavaConstructor.java

public class TestJNIConstructor {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // 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));
   }
}

This class declares a getIntegerObjectnative method, which accepts an int data, and then creates an object of type Integer in the native code, and its value is this value.

C code implementation:TestJavaConstructor.c

#include <jni.h>
#include <stdio.h>
#include "TestJNIConstructor.h"

JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
    
    
   // Get a class reference for java.lang.Integer
   jclass cls = (*env)->FindClass(env, "java/lang/Integer");

   // Get the Method ID of the constructor which takes an int
   jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
   if (NULL == midInit) return NULL;
   // Call back constructor to allocate a new instance, with an int argument
   jobject newObj = (*env)->NewObject(env, cls, midInit, number);

   // Try runnning the toString() on this newly create object
   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;
}

The functions used to create objects (jobject) in JNI are:

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);
   // Constructs a new Java object. The method ID indicates which constructor method to invoke

jobject AllocObject(JNIEnv *env, jclass cls);
  // Allocates a new Java object without invoking any of the constructors for the object.

Object (object) array
JNI program:TestJNIObjectArray.java

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]);
   }
}

For simplicity, this class declares a native method, which accepts an array of Integer type, and then calculates the sum and average of the numbers in the array in the native code, and then returns the two numbers in the form of Double array.
C code implementation:TestJNIObjectArray.c

#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"

JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
    
    
   // Get a class reference for java.lang.Integer
   jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
   // Use Integer.intValue() to retrieve the int
   jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
   if (NULL == midIntValue) return NULL;

   // Get the value of each Integer object in the array
   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);

   // Get a class reference for java.lang.Double
   jclass classDouble = (*env)->FindClass(env, "java/lang/Double");

   // Allocate a jobjectArray of 2 java.lang.Double
   jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);

   // Construct 2 Double objects by calling the constructor
   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;
}

Unlike an array of primitive data types, you need to use Get|SetObjectArrayElement()a function to process each element.
JNI provides array(jobjectArray)the following functions to create objects:

jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
   // Constructs a new array holding objects in class elementClass.
   // All elements are initially set to initialElement.

jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
   // Returns an element of an Object array.

void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
   // Sets an element of an Object array.

5. Local and global references

Managing references is key to writing efficient programs. FindClass(), GetMethodID()For example, we will often use a jclass, jmethodID and jfieldID in the local code GetFieldID(). In fact, these variables should only be obtained for the first time, and then used directly, instead of obtaining them every time, which can improve the efficiency of program execution.
JNI divides object references in native code into two types: local and global references:

  1. Native references are created in native code and are freed when the function exits or returns. Its valid scope is only inside the native function. You can also call DeleteLocalRef()to explicitly invalidate a local reference so that it can be reclaimed during garbage collection. Object references passed as parameters to local functions are local references, and all java objects (jobjects) returned from JNI functions are local references.
  2. Global references will remain until the programmer calls DeleteGlobalRef()free to manually free them, you can use NewGlobalRef()the function to create a global reference from a local reference.
    Below we give an example.
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));
   }
}

The JNI program above declares two nativefunctions , both of which create and return java.lang.Integerobjects. In the C code implementation, we need to java.lang.Integerget the class reference, and then we find the constructor from it method ID, and then call the constructor. However, we hope to cacheclass the references we get so that we don't have to get them again the next time we use them. Here's our C code, which we hope will work (but it doesn't!!!):Method ID

#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);
}

In the above program, we call to FindClass()obtain java.lang.Integerthe class reference, and then save it in a global static variable. However, on the second call the reference is invalidated (it's not NULL). This is because FindClass()the local class reference is returned, and once getIntegerthe function returns, the local reference becomes invalid.
To fix this, we need to create a global reference from the local reference and then assign it to the global staticvariable:

// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
    
    
   printf("Find java.lang.Integer\n");
   // FindClass returns a local reference
   jclass classIntegerLocal = (*env)->FindClass(env, "java/lang/Integer");
   // Create a global reference from the local reference
   classInteger = (*env)->NewGlobalRef(env, classIntegerLocal);
   // No longer need the local reference, free it!
   (*env)->DeleteLocalRef(env, classIntegerLocal);
}

Note that jmethodIDand jfieldIDare not jobject, so they cannot create a global reference! !

Guess you like

Origin blog.csdn.net/duoduo_11011/article/details/131172581