0%

JNI 使用总结

JNI 语法

JNI 的 C 函数

1
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

命名规则:Java_{package_and_classname}_{function_name}(JNI_arguments)

  • JNIEnv*: reference to JNI environment, which lets you access all the JNI functions.
  • jobject: reference to “this” Java object.

The extern “C” is recognized by C++ compiler only. It notifies the C++ compiler that these functions are to be compiled using C’s function naming protocol instead of C++ naming protocol.

Basics

  1. Java Primitives: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean for Java Primitive of int, byte, short, long, float, double, char and boolean, respectively.
  2. Java Reference Types: jobject for java.lang.Object. It also defines the following sub-types:
    jclass for java.lang.Class.
    jstring for java.lang.String.
    jthrowable for java.lang.Throwable.
    jarray for Java array. Java array is a reference type with eight primitive array and one Object array. Hence, there are eight array of primitives jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray and jbooleanArray; and one object array jobjectArray.

C 类型和 JNI 类型之间的转化,例如 jstring to a C-string, jintArray to C’s int[]. Primitive JNI types such as jint and jdouble do not need conversion and can be operated directly.

JNI function : parameter JNI Type convert to Native type resolved convert to JNI Type return.

JNI is a C interface, which is not object-oriented. It does not really pass the objects.

Passing Arguments and Result between Java & Native Programs

Passing Primitives

It is interesting to note that jint is mapped to C’s long (which is at least 32 bits), instead of of C’s int (which could be 16 bits). Hence, it is important to use jint in the C program, instead of simply using int.

convert:

1
2
3
4
5
6
7
8
9
10
11
12
// 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;

Passing Strings

Java’s String is an object (reference type), while C-string is a NULL-terminated char array

convert between Java String (represented as JNI jstring) and C-string (char*).

  1. jstring to char*
1
const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)
  1. char* to jstring
1
jstring NewStringUTF(JNIEnv*, char*)

JNI string functions:

1
2
3
4
5
6
7
8
9
10
11
// Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Informs the VM that the native code no longer needs access to utf.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
// Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
// Returns the length in bytes of the modified UTF-8 representation of a string.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
// Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding
// and place the result in the given buffer buf.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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*)
// The 3rd parameter isCopy (of jboolean*), which is an "in-out" parameter, will be set to JNI_TRUE if the returned string is a copy of the original java.lang.String instance. It will be set to JNI_FALSE if the returned string is a direct pointer to the original String instance - in this case, the native code shall not modify the contents of the returned string. The JNI runtime will try to return a direct pointer, if possible; otherwise, it returns a copy. Nonetheless, we seldom interested in modifying the underlying string, and often pass a NULL pointer.
const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL); // 第三个参数为 NULL,直接返回一个 string 的指针,无法修改指针指向的 char 值
// The GetStringUTFChars() function can be used to create a new C-string (char*) from the given Java's jstring. The function returns NULL if the memory cannot be allocated. It is always a good practice to check against NULL.
if (NULL == inCStr) return NULL;

// Step 2: Perform its intended operations
printf("In C, the received string is: %s\n", inCStr);
// Always invoke ReleaseStringUTFChars() whenever you do not need the returned string of GetStringUTFChars() to release the memory and the reference so that it can be garbage-collected.
(*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
// The NewStringUTF() function create a new JNI string (jstring), with the given C-string.
return (*env)->NewStringUTF(env, outCStr);
}

Passing Array of Primitives

convert:

  1. jintArray to jint[] : jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)

  2. jint[] to jintArray :

    1
    2
    3
    4
    // allocate
    jintArray NewIntArray(JNIEnv *env, jsize len)
    // copy
    void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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[]
// The GET|Release<PrimitiveType>ArrayElements() can be used to create a new C's native array jxxx[] from the given Java jxxxArray.
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
// The New<PrimitiveType>Array() can be used to allocate a new jxxxArray of a given size. You can then use the Set<PrimitiveType>ArrayRegion() function to fill its contents from a native array jxxx[].
jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2); // allocate
if (NULL == outJNIArray) return NULL;
(*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray); // copy
return outJNIArray;
}

api:

1
2
3
4
5
6
7
8
9
10
11
// 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);
// The Get|ReleasePrimitiveArrayCritical() functions does not allow blocking calls in between the get and release.
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

Accessing Object’s Variables and Calling Back Methods

Accessing Object’s Instance Variables

api:

1
2
3
4
5
6
7
8
9
10
// Returns the class of an object.
jclass GetObjectClass(JNIEnv *env, jobject obj);

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

// Get/Set the value of an instance variable of an object
// <type> includes each of the eight primitive types plus Object.
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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);

// For a Java class, the field descriptor is in the form of "L<fully-qualified-name>;", with dot replaced by forward slash (/), e.g.,, the class descriptor for String is "Ljava/lang/String;". For primitives, use "I" for int, "B" for byte, "S" for short, "J" for long, "F" for float, "D" for double, "C" for char, and "Z" for boolean. For arrays, include a prefix "[", e.g., "[Ljava/lang/Object;" for an array of Object; "[I" for an array of int.
// 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);
}

Accessing Class’ Static Variables

api:

1
2
3
4
5
6
7
// Returns the field ID for a static variable of a class.
jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);

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

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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);
}

Callback Instance Methods and Static Methods

api:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Returns the method ID for an instance method of a class or interface.
jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);

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

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

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

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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);
}
  • call back an instance method from the native code:
  1. Get a reference to this object’s class via GetObjectClass().
  2. From the class reference, get the Method ID via GetMethodID(). You need to provide the method name and the signature. The signature is in the form “(parameters)return-type”. You can list the method signature for a Java program via javap utility (Class File Disassembler) with -s (print signature) and -p (show private members):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> 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;
.......
  1. Based on the Method ID, you could invoke CallMethod() or CallVoidMethod() or CallObjectMethod(), where the return-type is , void and Object, respectively. Append the argument, if any, before the argument list. For non-void return-type, the method returns a value.
  • callback a static method:
    use GetStaticMethodID(), CallStaticMethod(), CallStaticVoidMethod() or CallStaticObjectMethod().

Callback Overridden Superclass’ Instance Method

steps:

  1. Get the Method ID, via GetMethodID().
  2. Based on the Method ID, invoke one of the CallNonvirtualMethod(), with the object, superclass, and arguments.

api:

1
2
3
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);

Creating Objects and Object Arrays

You can construct jobject and jobjectArray inside the native code, via NewObject() and newObjectArray() functions, and pass them back to the Java program.

Callback the Constructor to Create a New Java Object in the Native Code

api:

1
2
3
4
5
6
7
8
9
jclass FindClass(JNIEnv *env, const char *name);

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

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

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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 running 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);

//May need to call releaseStringUTFChars() before return
return newObj;
}

Array of Objects

api:

1
2
3
4
5
6
7
8
9
// Constructs a new array holding objects in class elementClass.
// All elements are initially set to initialElement.
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);

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

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

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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;
}

Local and Global References

Managing references is critical in writing efficient programs. For example, we often use FindClass(), GetMethodID(), GetFieldID() to retrieve a jclass, jmethodID and jfieldID inside native functions. Instead of performing repeated calls, the values should be obtained once and cached for subsequent usage, to eliminate the overheads.

The JNI divides object references (for jobject) used by the native code into two categories: local and global references:

  1. A local reference is created within the native method, and freed once the method exits. It is valid for the duration of a native method. You can also use JNI function DeleteLocalRef() to invalidate a local reference explicitly, so that it is available for garbage collection intermediately. Objects are passed to native methods as local references. All Java objects (jobject) returned by JNI functions are local references.
  2. A global reference remains until it is explicitly freed by the programmer, via the DeleteGlobalRef() JNI function. You can create a new global reference from a local reference via JNI function NewGlobalRef().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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 invoke FindClass() to find the class reference for java.lang.Integer, and saved it in a global static variable. Nonetheless, in the next invocation, this reference is no longer valid (and not NULL). This is because FindClass() returns a local reference, which is invalidated once the method exits.

To overcome the problem, we need to create a global reference from the local reference returned by FindClass(). We can then free the local reference. The revised code is as follows:

1
2
3
4
5
6
7
8
9
10
// 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);
}

Take note that jmethodID and jfieldID are not jobject, and cannot create global reference.

Oracle: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html
Android: http://developer.android.com/guide/practices/jni.html

JNIEnv 提供了大部分 JNI 函数。您的原生函数都会收到 JNIEnv 作为第一个参数。

在 Android Sudio 中使用 JNI

链接库

target_link_libraries 是 CMake 中用于将共享库或静态库链接到目标可执行文件或共享库的命令。

1
2
// 其中,<target> 是要链接库的目标文件的名称,item1、item2 等是要链接到目标文件的库的名称。
target_link_libraries(<target> [item1] [item2] [...])

example:

1
2
add_library(mylib SHARED mylib.cpp)
target_link_libraries(myexe mylib)
1
2
3
add_library(mylib1 SHARED mylib1.cpp)
add_library(mylib2 SHARED mylib2.cpp)
target_link_libraries(mylib2 mylib1)

上述代码将创建两个共享库 mylib1 和 mylib2,并将 mylib1 链接到 mylib2 中。这意味着,在链接 mylib2 时,CMake 将自动链接 mylib1。

so 文件的生成位置

\app\build\intermediates\cmake\debug\obj

module build.gradle setting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
}
}

ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
}
}

externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

CMakeLists.txt 写法

located under folder /app

1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.4.1)

add_library(fibonacci SHARED
jni/fibonacci.cpp) // fibonacci.cpp is located under /app/jni/fibonacci.cpp

find_library(log-lib log)

target_link_libraries(fibonacci ${log-lib})

Add multi cpp files

1
2
3
4
5
6
7
8
9
10
add_library( # Specifies the name of the library.
native-lib

# Sets the library as a shared library.
// 此处使用 SHARED 标志将库设置为共享库。如果需要将库设置为静态库,则应使用 STATIC 标志。
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp
src/main/cpp/another-file.cpp )

comment for a piece of code

1
2
3
4
5
6
7
8
9
10
11
// extern "C":C++ 中的一个关键字,用来指定函数使用 C 语言的命名和调用约定。在 JNI 中使用 C 的命名和调用约定非常重要,因为 Java 虚拟机只能通过 C 调用本地函数。因此,需要将 C++ 代码中的函数标记为 C 语言的函数,以便能够正确地被 Java 虚拟机调用。
// JNIEXPORT:JNI 中的宏,用来指定函数的可见性和调用约定。JNIEXPORT 宏被定义为 C 语言的 extern,用于声明一个导出函数
// JNICALL:JNI 中的宏,用来指定函数的调用约定。在 Windows 上,函数的调用约定为 __stdcall,而在其他操作系统上,函数的调用约定为默认的 C 语言调用约定。
extern "C" JNIEXPORT jint JNICALL
// Java_com_cmder_jnitest_MainActivity_calculateFibonacci:JNI 函数的名称,其中 Java_ 是 JNI 的命名约定,com_cmder_jnitest_MainActivity 是 Java 类的全限定名,calculateFibonacci 是 Java 方法的名称
// JNIEnv* env:JNI 函数的参数,表示指向 JNI 环境的指针。JNI 环境是一个结构体,包含了许多函数指针,用于操作 Java 对象和调用 Java 方法。
// jobject thiz:JNI 函数的参数,表示调用本地函数的 Java 对象的引用。在本例中,它是 MainActivity 类的一个实例。
// jint n:JNI 函数的参数,表示一个 Java int 类型的整数,它将传递给 C/C++ 代码。
Java_com_cmder_jnitest_MainActivity_calculateFibonacci(JNIEnv* env, jobject thiz, jint n) {
return fibonacci(n);
}

add so files in Android Studio

1
2
3
4
5
6
7
8
9
10
11
android {
// ...
sourceSets {
main {
// 4 so files, auto adapt
jniLibs.srcDirs = ['src/main/jniLibs']
// or
jniLibs.srcDirs = ['libs']
}
}
}

在 Java 中使用 JNI

  1. 在 src/com/cmder 目录下,创建 Java 类
    1
    2
    3
    4
    5
    6
    7
    public class MyJNI {
    static {
    System.loadLibrary("MyLibrary");
    }

    public native void myFunction();
    }
  2. 在 src 目录下,生成 JNI 头文件
    1
    2
    3
    javac -h . com/cmder/MyJNI.java // 这个方法在对MainActivity直接使用时出现问题
    // 改用
    javah com.example.jni.MainActivity
  3. 在 src 目录下,编写 C 代码
    1
    2
    3
    4
    5
    6
    7
    #include <stdio.h>
    #include <jni.h>
    #include "MyJNI.h"

    JNIEXPORT void JNICALL Java_com_cmder_MyJNI_myFunction(JNIEnv *env, jobject obj) {
    printf("Hello from C!\n");
    }
  4. 在 src 目录下,编译 C 代码创建动态链接库
    1
    gcc -shared -fpic -o libMyLibrary.so MyJNI.c -I/usr/lib/jvm/java-1.11.0-openjdk-amd64/include -I/usr/lib/jvm/java-1.11.0-openjdk-amd64/include/linux
    其中,-I 参数用于指定 Java 头文件的位置。
  5. 在 src 目录下,设置共享库
    1
    export LD_LIBRARY_PATH=.
  6. 在 src 目录下,运行 Java 代码
    1
    java com.cmder.MyJNI

CPU 架构模式

  1. arm64-v8a 现在基本 Android 以这个为主

  2. armeabi-v7a 2015 早期的手机

  3. armeabi 2011 基本没人用

  4. x86

  5. x86_64 模拟器

查看手机 CPU 架构 :

1
adb shell getprop ro.product.cpu.abi