最近由于公司项目的原因开始接触WebRTC,其中Android相关部分由于需要跨越了两种不同的语
言,因此需要一种机制能够让C/C++和JAVA之间进行交互,而JNI就是这样一种机制。通过JNI可
以实现C/C++和JAVA之前需要交互。本篇笔记的首先从一个实际的例子开始介绍JNI操作的完整流
程是怎样的;接着将就这个例子完整介绍JNI中需要注意的点。
image

JNI操作完整流程介绍

  • 编写Java类及声明Native方法
    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    public class Java2C {
    static {
    System.loadLibrary("jni");
    }

    // 成员变量
    private int number = 88;
    private static double speed = 55.66;
    private String message = "Hello from Java.";

    // 基本数据类型
    private native double average(int n1, int n2);

    // 引用类型
    private native String sayHello(String msg);

    // 基本数据类型数组
    private native double[] sumAndAverage(int[] numbers);

    // 引用类型数组
    private native String[] num2Str(int[] numbers);

    // 操作java成员变量
    private native void modifyJavaVariable();

    // 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.";
    }

    private native void testCallbackMethod();

    public static void main(String[] args) {
    Java2C javaClass = new Java2C();

    double aver = javaClass.average(5, 6);
    System.out.println("primitive type: the average of 5 and 6 is " + aver);

    System.out.println("Reference type: ");
    String javaString = javaClass.sayHello("Hello From Java");
    System.out.println(javaString);

    System.out.println("primitive type array: ");
    int[] numbers = {11, 22, 33};
    double[] results = javaClass.sumAndAverage(numbers);
    System.out.println("In Java, the sum is " + results[0]);
    System.out.println("In Java, the average is " + results[1]);

    System.out.println("reference type array: ");
    String[] numStrs = javaClass.num2Str(numbers);
    System.out.println("In Java the string is " + numStrs[0] + " " + numStrs[1] + " " + numStrs[2]);


    System.out.println("Operate java variable: ");
    javaClass.modifyJavaVariable();

    System.out.println("C/C++ call java viarable and method");
    javaClass.testCallbackMethod();
    }
    }
  • 生成jni native头文件

    javac -h <头文件存放路径> <java类>

    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class Java2C */

    #ifndef _Included_Java2C
    #define _Included_Java2C
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
    * Class: Java2C
    * Method: average
    * Signature: (II)D
    */
    JNIEXPORT jdouble JNICALL Java_Java2C_average
    (JNIEnv *, jobject, jint, jint);

    /*
    * Class: Java2C
    * Method: sayHello
    * Signature: (Ljava/lang/String;)Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_Java2C_sayHello
    (JNIEnv *, jobject, jstring);

    /*
    * Class: Java2C
    * Method: sumAndAverage
    * Signature: ([I)[D
    */
    JNIEXPORT jdoubleArray JNICALL Java_Java2C_sumAndAverage
    (JNIEnv *, jobject, jintArray);

    /*
    * Class: Java2C
    * Method: num2Str
    * Signature: ([I)[Ljava/lang/String;
    */
    JNIEXPORT jobjectArray JNICALL Java_Java2C_num2Str
    (JNIEnv *, jobject, jintArray);

    /*
    * Class: Java2C
    * Method: modifyJavaVariable
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_Java2C_modifyJavaVariable
    (JNIEnv *, jobject);

    /*
    * Class: Java2C
    * Method: testCallbackMethod
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_Java2C_testCallbackMethod
    (JNIEnv *, jobject);

    #ifdef __cplusplus
    }
    #endif
    #endif

  • 在C文件中编写相关jni native方法的实现
    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    #include <jni.h>
    #include <stdio.h>
    #include "Java2C.h"

    JNIEXPORT jdouble JNICALL Java_Java2C_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;
    return result;
    }

    JNIEXPORT jstring JNICALL Java_Java2C_sayHello
    (JNIEnv *env, jobject thisObj, jstring inJNIStr) {
    const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
    if (NULL == inCStr) return NULL;

    printf("In C, the receiving string is: %s\n", inCStr);
    (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);

    char outCStr[128] = "Out string from C";
    return (*env)->NewStringUTF(env, outCStr);
    }

    JNIEXPORT jdoubleArray JNICALL Java_Java2C_sumAndAverage
    (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
    jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
    if (NULL == inCArray) return NULL;
    jsize length = (*env)->GetArrayLength(env, inJNIArray);

    jint sum = 0;
    int i;
    for (i = 0;i<length;i++) {
    sum += inCArray[i];
    }
    jdouble average = (jdouble)sum / length;
    (*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0);

    jdouble outArray[] = {sum, average};

    jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);
    if (NULL == outJNIArray) return NULL;
    (*env)->SetDoubleArrayRegion(env, outJNIArray, 0, 2, outArray);
    return outJNIArray;
    }

    JNIEXPORT jobjectArray JNICALL Java_Java2C_num2Str
    (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
    jobjectArray ret;
    jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
    if (NULL == inCArray) return NULL;
    jsize length = (*env)->GetArrayLength(env, inJNIArray);

    char cStrArray[6][128];
    int i;
    for (i = 0;i < length; i++) {
    snprintf(cStrArray[i], 128, "%d", inCArray[i]);
    }

    jclass strClass = (*env)->FindClass(env, "java/lang/String");
    ret = (*env)->NewObjectArray(env, length, strClass,
    (*env)->NewStringUTF(env, ""));
    for (i=0;i<length;i++) {
    (*env)->SetObjectArrayElement(env, ret, i,
    (*env)->NewStringUTF(env, cStrArray[i]));
    }
    return ret;
    }

    JNIEXPORT void JNICALL Java_Java2C_modifyJavaVariable
    (JNIEnv *env, jobject thisObj) {
    jclass thisClass = (*env)->GetObjectClass(env, thisObj);

    jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
    if (NULL == fidNumber) return;

    jint number = (*env)->GetIntField(env, thisObj, fidNumber);
    printf("In C, thi int is %d\n", number);

    number = 99;
    (*env)->SetIntField(env, thisObj, fidNumber, number);

    jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
    if (NULL == fidMessage) return ;

    jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);

    const char* cStr = (*env)->GetStringUTFChars(env, message, NULL);
    if (NULL == cStr) return ;

    printf("In C, the string is %s\n", cStr);
    (*env)->ReleaseStringUTFChars(env, message, cStr);

    message = (*env)->NewStringUTF(env, "Hello from C");
    if (NULL == message) return ;

    (*env)->SetObjectField(env, thisObj, fidMessage, message);

    jfieldID fidSpeed = (*env)->GetStaticFieldID(env, thisClass, "speed", "D");
    if (NULL == fidSpeed) return ;

    jdouble speed = (*env)->GetStaticDoubleField(env, thisClass, fidSpeed);
    printf("In C, the speed is %f\n", speed);
    speed = 77.99;
    (*env)->SetStaticDoubleField(env, thisClass, fidSpeed, speed);
    }

    JNIEXPORT void JNICALL Java_Java2C_testCallbackMethod
    (JNIEnv *env, jobject thisObj) {
    jclass thisClass = (*env)->GetObjectClass(env, thisObj);

    jmethodID midCallback = (*env)->GetMethodID(env, thisClass, "callback", "(Ljava/lang/String;)V");
    if (NULL == midCallback) return ;
    printf("In C, call back Java's callback(String)\n");
    jstring message = (*env)->NewStringUTF(env, "Hello from C");
    (*env)->CallVoidMethod(env, thisObj, midCallback, message);

    jmethodID midCallbackAverage = (*env)->GetMethodID(env, thisClass, "callbackAverage", "(II)D");
    if (NULL == midCallbackAverage) return ;
    printf("In C, call back Java's callbackAverage\n");
    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) {
    printf("1111\n");
    return ;
    }
    jstring resultJNIStr = (*env)->CallStaticObjectMethod(env, thisClass, midCallbackStatic);
    if (NULL == resultJNIStr) return ;
    const char* resultCStr = (*env)->GetStringUTFChars(env, resultJNIStr, NULL);
    if (NULL == resultCStr) return;
    printf("In C, the returned string is %s\n", resultCStr);
    }
  • 通过gcc/g++ 生成动态库

    gcc -fPIC -I”$JAVA_HOME/include” -I”$JAVA_HOME/include/linux” -shared -o libjni.so Java2C.c

  • 运行进行测试

    java -Djava.library.path=. Java2C
    这里需要使用-D指定前面生的动态库路径,否则运行的时候会提示找不到动态库的错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    In C, the numbers are 5 and 6
    primitive type: the average of 5 and 6 is 5.5
    Reference type:
    In C, the receiving string is: Hello From Java
    Out string from C
    primitive type array:
    In Java, the sum is 66.0
    In Java, the average is 22.0
    reference type array:
    In Java the string is 11 22 33
    Operate java variable:
    In C, thi int is 88
    In C, the string is Hello from Java.
    In C, the speed is 55.660000
    C/C++ call java viarable and method
    In C, call back Java's callback(String)
    In Java with Hello from C
    In C, call back Java's callbackAverage
    In C, the average is 2.500000
    In C, the returned string is From static Java method.

    JNI中类型的对应关系与转换

  • 基于数据类型
Java类型 Native Type 描述
boolean jboolean C/C++无符号的8位整型(unsigned char)
byte jbyte C/C++带符号的8位整型(char)
char jchar C/C++无符号的16位整型(unsigned short)
short jshort C/C++带符号的16位整型 (signed short)
int jint C/C++带符号的32位整型(int)
long jlong C/C++带符号的64位整型(long)
float jfloat C/C++32位浮点型(float)
double jdouble C/C++64位浮点型(double)
  • 引用数据类型
Java类型 Native Type 描述
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class类对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 单精度浮点型数组
double[] jdouble 双精度浮点型数组
void void n/a
  • 引用类型的继承关系
    image

  • 注意

    1. 基本数据类型可以在native层直接使用
    2. 引用数据类型则不能直接使用,需要根据JNI函数进行相应的转换才能使用
    3. 多维数据(包括二维数组)都是引用类型,需要使用jobjectArray类型存取其值
  • JNI相关操作方法

    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
    // native string方法
    // 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
1
2
3
4
5
6
7
8
9
10
11
// JNI Primitive Array Function
// 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);

C/C++调用Java类中的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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.

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.

C/C++调用Java类中的成员方法

通过下面这条命令可以获取到java中方法的签名

javas -s -p Java2C

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
Compiled from "Java2C.java"
public class Java2C {
private int number;
descriptor: I
private static double speed;
descriptor: D
private java.lang.String message;
descriptor: Ljava/lang/String;
public Java2C();
descriptor: ()V

private native double average(int, int);
descriptor: (II)D

private native java.lang.String sayHello(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;

private native double[] sumAndAverage(int[]);
descriptor: ([I)[D

private native java.lang.String[] num2Str(int[]);
descriptor: ([I)[Ljava/lang/String;

private native void modifyJavaVariable();
descriptor: ()V

private void callback(java.lang.String);
descriptor: (Ljava/lang/String;)V

private double callbackAverage(int, int);
descriptor: (II)D

private static java.lang.String callbackStatic();
descriptor: ()Ljava/lang/String;

private native void testCallbackMethod();
descriptor: ()V

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V

static {};
descriptor: ()V
}

再通过下面这些JNI方法变可以在C/C++代码中调用到JAVA类的成员方法了

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

参考

[1] Java Programming Tutorial Java Native Interface (JNI)