一、基本類型
Java 類型 | 本地類型 | 描述 | C 類型 |
---|---|---|---|
int | jint | signed 32 bits | 根據平臺不同 |
long | jlong | signed 64 bits | 根據平臺不同 |
byte | jbyte | signed 8 bits | 根據平臺不同 |
char | jchar | unsigned 16 bits | typedef unsigned short |
short | jshort | singed 16 bits | typedef short |
boolean | jboolean | unsigned 8 bits | typedef unsigned char |
float | jfloat | 32 bits | typedef float |
double | jdouble | 64 bits | typedef double |
void | void | N/A | N/A |
二、引用類型性
JNI 中的引用類型主要包括:
- 類;
- 對象;
- 數組。
和 Java 中的引用類型的對應關系如下表所示:
Java 類型 | JNI 類型 | 描述 |
---|---|---|
Object | jobject | Object 類型 |
Class | jclass | Class 類型 |
String | jstring | String 類型 |
Object[] | jobjectArray | 對象數組 |
boolean[] | jbooleanArray | boolean 數組 |
byte[] | jbyteArray | byte 數組 |
char[] | jcharArray | char 數組 |
short[] | jshortArray | short 數組 |
int[] | jintArray | int 數組 |
long[] | jlongArray | long 數組 |
float[] | jfloatArray | float 數組 |
double[] | jdoubleArray | double 數組 |
Throwable | jthrowable | Throwable |
三、native 函數參數
native 函數參數說明:
每個 native 函數,都至少有兩個參數:
JNIEnv*;
jclass 或 jobject:
- 當 native 方法為靜態方法時:jclass 代表 native 方法所屬類的 class 對象。
- 當 native 方法為非靜態方法時:jobject 代表 native 方法所屬的對象。
四、屬性與方法簽名
數據類型 | 簽名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
reference | LClassname; |
type[] | [type |
method | type(arg-types)ret-type |
熟悉 JVM 的一定會發現這就是 JVM 中的字段描述符和方法描述符。
需要注意的是:
- 類描述符開頭的 'L' 與結尾的 ';' 必須要有;
- 數組描述符,開頭的 '[' 必須要有;
- 方法描述符規則: "(各參數描述符)返回值描述符",其中參數描述符間沒有任何分隔符號。
五、C/C++ 訪問 Java 的屬性、方法
為了能夠在 C/C++ 中調用 Java 中的類,jni.h 的頭文件專門定義了 jclass 類型表示 Java 中 Class 類。JNIEnv 中有 3 個
函數可以獲取 jclass。
jclass FindClass(const char* clsName)
通過類的名稱(類的全名,這時候包名不是用"."點號而是用"/"來區分的)來獲取 jclass。jclass GetObjectClass(jobject obj)
通過對象實例來獲取 jclass,相當于 Java 中的 getClass() 函數jclass getSuperClass(jclass obj)
通過 jclass 可以獲取其父類的 jclass 對象
在 JNI 調用中,肯定會涉及到本地方法操作 Java 類中數據和方法。JNI 要求程序員通過特殊的 JNI 函數來獲取和設置數據以及調用 java 方法。
有以下幾種情況:
- 訪問 Java 類的非靜態屬性;
- 訪問 Java 類的靜態屬性;
- 訪問 Java 類的非靜態方法;
- 訪問 Java 類的靜態方法;
- 間接訪問 Java 類的父類的方法;
- 訪問 Java 類的構造方法。
在 Native 本地代碼中訪問 Java 層的代碼,一個常用的常見的場景就是獲取 Java 類的屬性和方法。所以為了在 C/C++ 獲取 Java 層的屬性和方法,JNI 在 jni.h 頭文件中定義了 jfieldID 和 jmethodID 這兩種類型來分別代表 Java 端的屬性和方法。
在訪問或者設置 Java 某個屬性的時候,首先就要現在本地代碼中取得代表該 Java 類的屬性的 jfieldID,然后才能在本地代碼中進行 Java 屬性的操作,同樣,在需要調用 Java 類的某個方法時,也是需要取得代表該方法的 jmethodID 才能進行 Java 方法操作。
5.1、訪問 Java 的非靜態屬性
Java 聲明如下:
public String name = "Jagger";
// 訪問非靜態屬性 name,修改它的值
// accessField 自定義的一個方法
public native void accessField();
C 代碼如下:
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_accessField
(JNIEnv *env, jobject jobject) {
// Jclass
jclass cls = (*env)->GetObjectClass(env, jobject);
// jfieldID 屬性名稱,屬性簽名
jfieldID fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
// 獲取屬性值
jstring jstr = (*env)->GetObjectField(env, jobject, fid);
...
}
5.2、訪問 Java 的靜態屬性
Java 聲明如下:
public static int count = 13;
public native void accessStaticField();
C 代碼如下:
JNIEXPORT void JNICALL Java_com_haocai_jni_JniTest_accessStaticField
(JNIEnv *env, jobject jobj) {
// jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
// jfieldID
jfieldID fid =(*env)->GetStaticFieldID(env, cls, "count", "I");
// GetStatic<Type>Field
jint count = (*env)->GetStaticIntField(env, cls, fid);
...
}
常見的調用 Java 層的方法一般是使用 JNIEnv 來進行操作:
GetFieldID/GetMethodID:獲取某個屬性/某個方法;
GetStaticFieldID/GetStaticMethodID:獲取某個靜態屬性/靜態方法。
方法的具體實現如下:
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
以上四個函數都是 4 個入參,而且每個入參的都是:
- *JNIEnv *env;
- jclass clazz;
- const char *name;
- const char *sig。
JNIEnv 代表一個 JNI 環境接口,jclass 上面也說了代表 Java 層中的“類”,name 則代表方法名或者屬性名。char *sig 代表代
表了 JNI 中的一個特殊字段 —— 簽名。
5.3、訪問 Java 的非靜態方法
Java 聲明如下:
public int genRandomInt(int max){
System.out.println("genRandomInt 執行了..");
return new Random().nextInt(max);
}
C 代碼如下:
JNIEXPORT void JNICALL Java_com_haocai_jni_JniTest_accessMethod
(JNIEnv *env, jobject jobj) {
//Jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
// JmethodID
jfieldID mFid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
// 調用
// Call<Type>Method
jint random = (*env)->CallIntMethod(env, jobj, mFid, 200);
...
}
5.4、訪問 Java 的靜態方法
Java 聲明如下:
public static String getUUID(){
return UUID.randomUUID().toString();
}
C 代碼如下:
// 訪問 Java 靜態方法
JNIEXPORT void JNICALL Java_com_haocai_jni_JniTest_accessStaticMethod
(JNIEnv *env, jobject jobj) {
// Jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
// JmethodID
jfieldID mFid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
// CallStatic<Type>Method
jstring uuid = (*env)->CallStaticObjectMethod(env, jobj, mFid);
...
}
5.5、訪問 Java 類的構造方法
Java 聲明如下:
public native long accessConstructor();
C 代碼如下:
JNIEXPORT jlong JNICALL Java_com_haocai_jni_JniTest_accesssConstructor
(JNIEnv *env, jobject jobj) {
// jclass
jclass cls = (*env)->FindClass(env, "java/util/Date");
// jmethodID
jmethodID constructor_mid= (*env)->GetMethodID(env, cls,"<init>","()V");
// 實例化一個 Date 對象(可以在 constructor_mid 后加參)
jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);
// 調用 getTime 方法
jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
jlong time = (*env)->CallLongMethod(env, date_obj, mid);
printf("time:%lld\n",time);
return time;
}
這里還有另一種方法來調用構造函數,方法如下:
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
這里多了一個參數,即 jvalue *args,這里是 args 代表的是對應構造函數的所有參數的,我們可以應將傳遞給構造函數的所有參數放在 jvalues 類型的數組 args 中,該數組緊跟著放在 methodID 參數的后面。