###JNI數據類型java
####基本數據 Java基本數據類型與JNI數據類型的映射關係數組
Java類型->JNI類型->C類型
複製代碼
JNI的基本數據類型有(左邊是Java,右邊是JNI):微信
boolean jboolean
byte jbyte
char jchar
short jshort
int jint
long jlong
float jfloat
double jdouble
void void
複製代碼
####引用類型(對象)dom
String jstring
Object jobject
數組,基本數據類型的數組
byte[] jByteArray
對象數組
object[](String[]) jobjectArray
複製代碼
###native函數參數說明ide
每一個native函數,都至少有兩個參數(JNIEnv*,jclass或者jobject)。函數
1)當native方法爲靜態方法時: jclass 表明native方法所屬類的class對象(JniTest.class)。測試
2)當native方法爲非靜態方法時: jobject 表明native方法所屬的對象。spa
######native函數的頭文件能夠本身寫。命令行
###關於屬性與方法的簽名code
####1、屬性的簽名
屬性的簽名其實就是屬性的類型的簡稱,對應關係以下:
尤爲注意的是,類的簽名格式就是:
L完整包名;
複製代碼
其中完整包名用 / 代替.
末尾的 ; 不能省略
數組的簽名就是:
[類型簽名
複製代碼
其中,多爲數組就用多個[
####2、方法的簽名
獲取方法的簽名比較麻煩一些,經過下面的方法也能夠拿到屬性的簽名。
打開命令行,輸入javap,出現如下信息:
上述信息告訴咱們,經過如下命令就能夠拿到指定類的全部屬性、方法的簽名了,很方便有木有?!
javap -s -p 完整類名
複製代碼
咱們經過cd命令,來到Java工程的bin目錄,而後輸入命令:
D:\WebPrj\TestJni\bin>javap -s -p com.test.JniTest
Compiled from "JniTest.java"
public class com.test.JniTest {
public java.lang.String str;
descriptor: Ljava/lang/String;
public static int NUM;
descriptor: I
static {};
descriptor: ()V
public com.test.JniTest();
descriptor: ()V
public static native java.lang.String getStringFromC();
descriptor: ()Ljava/lang/String;
public native void accessField();
descriptor: ()V
public native void accessStaticField();
descriptor: ()V
public int genRandomInt(int);
descriptor: (I)I
public native void accessMethod();
descriptor: ()V
public native void accessStaticMethod();
descriptor: ()V
public static java.lang.String getUUID();
descriptor: ()Ljava/lang/String;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
}
複製代碼
其中,descriptor就是咱們須要的簽名了,注意簽名中末尾的分號不能省略。
方法簽名的規律就是,括號不能夠省略:
(參數類型簽名)返回值類型簽名
複製代碼
###C/C++訪問Java的屬性、方法
有如下幾種狀況:
####1、訪問Java的非靜態屬性
Java方法中,經過調用accessField,利用C修改靜態屬性
public String str = "Li lu";
//訪問非靜態屬性str,修改它的值
public native void accessField();
複製代碼
C代碼以下:(頭文件能夠不寫,直接寫實現)
JNIEXPORT void JNICALL Java_com_test_JniTest_accessField
(JNIEnv * env, jobject jobj){
//經過對象拿到Class
jclass clz = (*env)->GetObjectClass(env, jobj);
//拿到對應屬性的ID
jfieldID fid = (*env)->GetFieldID(env, clz, "str", "Ljava/lang/String;");
//經過屬性ID拿到屬性的值
jstring jstr = (*env)->GetObjectField(env, jobj, fid);
//經過Java字符串拿到C字符串,第三個參數是一個出參,用來告訴咱們GetStringUTFChars內部是否複製了一份字符串
//若是沒有複製,那麼出參爲isCopy,這時候就不能修改字符串的值了,由於Java中常量池中的字符串是不容許修改的(可是jstr能夠指向另一個字符串)
char* cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
//在C層修改這個屬性的值
char res[20] = "I love you : ";
strcat(res, cstr);
//從新生成Java的字符串,而且設置給對應的屬性
jstring jstr_new = (*env)->NewStringUTF(env, res);
(*env)->SetObjectField(env, jobj, fid, jstr_new);
//最後釋放資源,通知垃圾回收器來回收
//良好的習慣就是,每次GetStringUTFChars,結束的時候都有一個ReleaseStringUTFChars與之呼應
(*env)->ReleaseStringUTFChars(env, jstr, cstr);
}
複製代碼
最後在Java中測試:
public static void main(String[] args) {
JniTest test = new JniTest();
System.out.println(test.str);
//修改非靜態屬性str
test.accessField();
System.out.println(test.str);
}
複製代碼
####2、訪問Java的靜態屬性
Java代碼以下:
//訪問靜態屬性NUM,修改它的值
public static int NUM = 1;
public native void accessStaticField();
複製代碼
C代碼以下:
JNIEXPORT void JNICALL Java_com_test_JniTest_accessStaticField
(JNIEnv * env, jobject jobj){
//與上面相似,只不過是某些方法須要加上Static
jclass clz = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetStaticFieldID(env, clz, "NUM", "I");
jint jInt = (*env)->GetStaticIntField(env, clz, fid);
jInt++;
(*env)->SetStaticIntField(env, clz, fid, jInt);
}
複製代碼
最後在Java中測試:
public static void main(String[] args) {
JniTest test = new JniTest();
System.out.println(NUM);
test.accessStaticField();
System.out.println(NUM);
}
複製代碼
####3、訪問Java的非靜態方法
Java代碼以下,經過調用accessMethod,在底層用C語言調用genRandomInt方法
//產生指定範圍的隨機數
public int genRandomInt(int max){
System.out.println("genRandomInt 執行了...max = "+ max);
return new Random().nextInt(max);
}
public native void accessMethod();
複製代碼
C代碼以下:
JNIEXPORT void JNICALL Java_com_test_JniTest_accessMethod
(JNIEnv * env, jobject jobj){
jclass clz = (*env)->GetObjectClass(env, jobj);
//拿到方法的ID,最後一個參數是方法的簽名
jmethodID mid = (*env)->GetMethodID(env, clz, "genRandomInt", "(I)I");
//調用該方法,最後一個是可變參數,就是調用該方法所傳入的參數
//套路是若是返回是:Call返回類型Method
jint jInt = (*env)->CallIntMethod(env, jobj, mid, 100);
printf("output from C : %d", jInt);
}
複製代碼
最後在Java中測試:
public static void main(String[] args) {
JniTest test = new JniTest();
test.accessMethod();
}
複製代碼
####4、訪問Java的靜態方法
Java代碼以下,經過調用accessStaticMethod,在底層用C語言調用getUUID方法
public native void accessStaticMethod();
//產生UUID字符串
public static String getUUID(){
System.out.println("getUUID 執行了...");
return UUID.randomUUID().toString();
}
複製代碼
C代碼以下:
JNIEXPORT void JNICALL Java_com_test_JniTest_accessStaticMethod
(JNIEnv * env, jobject jobj){
jclass clz = (*env)->GetObjectClass(env, jobj);
jmethodID mid = (*env)->GetStaticMethodID(env, clz, "getUUID", "()Ljava/lang/String;");
//調用java的靜態方法,拿到返回值
jstring jstr = (*env)->CallStaticObjectMethod(env, clz, mid);
//把拿到的Java字符串轉換爲C的字符串
char* cstr= (*env)->GetStringUTFChars(env, jstr, NULL);
//後續操做,產生以UUID爲文件名的文件
char fielName[100];
sprintf(fielName, "D:\\%s.txt", cstr);
FILE* f = fopen(fielName, "w");
fputs(cstr, f);
fclose(f);
printf("output from C : File had saved", jstr);
}
複製代碼
最後在Java中測試:
public static void main(String[] args) {
JniTest test = new JniTest();
test.accessStaticMethod();
}
複製代碼
####5、間接訪問Java類的父類的方法
Java代碼以下:
父類:
package com.test;
public class Human {
protected void speek() {
System.out.println("Human Speek");
}
}
複製代碼
子類:
package com.test;
public class Man extends Human {
@Override
protected void speek() {
// 能夠經過super關鍵字來訪問父類的方法
// super.speek();
System.out.println("Man Speek");
}
}
複製代碼
在TestJni類中有Human屬性:
//父類的引用指向子類的對象
Human man= new Man();
public native void accessNonvirtualMethod();
複製代碼
若是是直接使用man.speek()的話,訪問的是子類Man的方法 可是經過底層C的方式能夠間接訪問到父類Human的方法,跳過子類的實現,甚至你能夠直接哪一個父類(若是父類有多個的話),這是Java作不到的。
下面是C代碼實現,無非就是屬性和方法的訪問:
JNIEXPORT void JNICALL Java_com_test_JniTest_accessNonvirtualMethod
(JNIEnv * env, jobject jobj){
//先拿到屬性man
jclass clz=(*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, clz, "man", "Lcom/test/Human;");
jobject man = (*env)->GetObjectField(env, jobj, fid);
//拿到父類的類,以及speek的方法id
jclass clz_human = (*env)->FindClass(env, "com/test/Human");
jmethodID mid = (*env)->GetMethodID(env, clz_human, "speek", "()V");
//調用本身的speek實現
(*env)->CallVoidMethod(env, man, mid);
//調用父類的speek實現
(*env)->CallNonvirtualVoidMethod(env, man, clz_human, mid);
}
複製代碼
這裏直接使用CallVoidMethod,雖然傳進去的是父類的Method ID,可是訪問的讓然是子類的實現。
最後,經過CallNonvirtualVoidMethod,訪問不覆蓋的父類方法(C++使用virtual關鍵字來覆蓋父類的實現),固然你也能夠指定哪一個父類(若是有多個父類的話)。
最後在Java中測試:
public static void main(String[] args) {
JniTest test = new JniTest();
//這時候是調用子類Man的方法
test.man.speek();
//可是經過JNI的方式,能夠訪問到父類的speek方法
test.accessNonvirtualMethod();
}
複製代碼
####6、訪問Java類的構造方法
Java代碼以下,經過調用accessConstructor,在底層用C語言調用java.util.Date產生一個當前的時間戳,而且返回。
//調用Date的構造函數
public native long accessConstructor();
複製代碼
C代碼以下:
JNIEXPORT jlong JNICALL Java_com_test_JniTest_accessConstructor
(JNIEnv * env, jobject jobj){
jclass clz_date = (*env)->FindClass(env, "java/util/Date");
//構造方法的函數名的格式是:<init>
//不能寫類名,由於構造方法函數名都同樣區分不了,只能經過參數列表(簽名)區分
jmethodID mid_Date = (*env)->GetMethodID(env, clz_date, "<init>", "()V");;
//調用構造函數
jobject date = (*env)->NewObject(env, clz_date, mid_Date);
//注意簽名,返回值long的屬性簽名是J
jmethodID mid_getTime= (*env)->GetMethodID(env, clz_date, "getTime", "()J");
//調用getTime方法
jlong jtime = (*env)->CallLongMethod(env, date, mid_getTime);
return jtime;
}
複製代碼
最後在Java中測試:
public static void main(String[] args) {
JniTest test = new JniTest();
//直接在Java中構造Date而後調用getTime
Date date = new Date();
System.out.println(date.getTime());
//經過C語音構造Date而後調用getTime
long time = test.accessConstructor();
System.out.println(time);
}
複製代碼
####總結
屬性、方法的訪問的使用是和Java的反射API相似的。
###綜合進階案例——JNI返回中文亂碼問題
測試亂碼問題:
public native void testChineseIn(String chinese);//傳進去
public native String testChineseOut();//取出來會亂碼
public static void main(String[] args) {
//傳中文進去,而後轉爲C字符串,直接在C層輸出是沒有問題的
JniTest test = new JniTest();
test.testChineseIn("我愛你");
//C層將C字符串轉換爲JavaString而後輸出,就會亂碼
System.out.println(test.testChineseOut());
}
複製代碼
C代碼以下:
JNIEXPORT void JNICALL Java_com_test_JniTest_testChineseIn
(JNIEnv * env, jobject jobj, jstring chinese){
char* c_chinese = (*env)->GetStringUTFChars(env, chinese, NULL);
printf("%s", c_chinese);
}
JNIEXPORT jstring JNICALL Java_com_test_JniTest_testChineseOut
(JNIEnv * env, jobject jobj){
char* c_str = "我愛你";
jstring j_str = (*env)->NewStringUTF(env, c_str);
return j_str;
}
複製代碼
結果輸出,其中第一條是C返回的亂碼,第二條是傳進去在C層打印的結果:
ÎҰ®Ä
我愛你
複製代碼
######能夠看到C執行的速度要比Java快。
#####緣由分析,調用NewStringUTF的時候,產生的是UTF-16的字符串,可是咱們須要的時候UTF-8字符串。
解決辦法,經過Java的String類的構造方法來進行字符集變換。
JNIEXPORT jstring JNICALL Java_com_test_JniTest_testChineseOut
(JNIEnv * env, jobject jobj){
//須要返回的字符串
char* c_str = "我愛你";
//jstring j_str = (*env)->NewStringUTF(env, c_str);
//經過調用構造方法String string = new String(byte[], charsetName);來解決亂碼問題
//0.找到String類
jclass clz_String = (*env)->FindClass(env, "java/lang/String");
jmethodID mid = (*env)->GetMethodID(env, clz_String, "<init>", "([BLjava/lang/String;)V");
//準備new String的參數:byte數組以及字符集
//1.建立字節數組,而且將C的字符串拷貝進去
jbyteArray j_byteArray = (*env)->NewByteArray(env, strlen(c_str));
(*env)->SetByteArrayRegion(env, j_byteArray, 0, strlen(c_str), c_str);
//2.建立字符集的參數,這裏用Windows的more字符集GB2312
jstring charsetName = (*env)->NewStringUTF(env, "GB2312");
//調用
jstring j_new_str = (*env)->NewObject(env, clz_String, mid, j_byteArray, charsetName);
return j_new_str;
}
複製代碼
若是以爲個人文字對你有所幫助的話,歡迎關注個人公衆號:
個人羣歡迎你們進來探討各類技術與非技術的話題,有興趣的朋友們加我私人微信huannan88,我拉你進羣交(♂)流(♀)。