接續(xù)上篇JNI開(kāi)發(fā)系列②.h頭文件分析
前情提要
在前面 舌厨, 我們已經(jīng)熟悉了JNI的開(kāi)發(fā)流程 裙椭, .h頭文件的分析 , 生成頭文件javah
命令 , 以及java類(lèi)型在C語(yǔ)言中的表現(xiàn)形式 你雌, 值得注意的是 婿崭, java中的所有引用類(lèi)型都是jobject
類(lèi)型 氓栈, native
生成的函數(shù) 婿着, 以Java_全類(lèi)名_方法名
表示竟宋,包名的.
以_
表示 丘侠。
概述
在開(kāi)篇的時(shí)候 蜗字,我們就使用java的native
方法調(diào)用過(guò)C函數(shù) 脂新, 返回了一個(gè)String類(lèi)型的字符串 争便, 使用(*Env)->NewStringUTF(Env, "Jni C String");
函數(shù) 始花, 我們將字符指針轉(zhuǎn)換成jstring
酷宵, java類(lèi)型的字符串返回給了我們的java層 浇垦。今天我們來(lái)學(xué)習(xí) 荣挨, 使用C語(yǔ)言來(lái)調(diào)用Java的字段與方法 默垄。
part 1 : C 函數(shù)訪問(wèn)java字段
一 口锭, 定義Java 的
String
類(lèi)型字段與修改字段的native
方法
// 使用C語(yǔ)言修改java字段
public String name = "zeno" ;
// C語(yǔ)言修改java String 類(lèi)型字段本地方法
public native void accessJavaStringField() ;
// 調(diào)用
HelloJni jni = new HelloJni() ;
System.out.println("修改前 name 的值:"+jni.name);
//C語(yǔ)言修改java字段本地方法
jni.accessJavaStringField();
System.out.println("修改后 name 的值:"+jni.name);
二 鹃操, 在C語(yǔ)言頭文件中定義
native
方法的實(shí)現(xiàn)函數(shù) 荆隘, 并實(shí)現(xiàn)
// com.zeno.jni_HelloJNI.h
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField
(JNIEnv *, jobject);
// Hello_JNI.c
/*C語(yǔ)言訪問(wèn)java String類(lèi)型字段*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField
(JNIEnv *env, jobject jobj) {
// 得到j(luò)class
jclass jcls = (*env)->GetObjectClass(env, jobj);
// 得到字段ID
jfieldID jfID = (*env)->GetFieldID(env, jcls, "name", "Ljava/lang/String;");
// 得到字段的值
jstring jstr = (*env)->GetObjectField(env, jobj, jfID);
// 將jstring類(lèi)型轉(zhuǎn)換成字符指針
char* cstr = (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
//printf("is vaule:%s\n", cstr);
// 拼接字符
char text[30] = " xiaojiu and ";
strcat(text, cstr);
//printf("modify value %s\n", text);
// 將字符指針轉(zhuǎn)換成jstring類(lèi)型
jstring new_str = (*env)->NewStringUTF(env, text);
// 將jstring類(lèi)型的變量 , 設(shè)置到j(luò)ava 字段中
(*env)->SetObjectField(env, jobj, jfID, new_str);
}
三 乱陡, 輸出
修改前 name 的值:zeno
修改后 name 的值: xiaojiu and zeno
四 仪壮, 分析
首先來(lái)分析C函數(shù):
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField
(JNIEnv *env, jobject jobj)
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC
(JNIEnv *Env, jclass jclazz)
我們可以看出兩處不同 爽彤, 一處是返回值類(lèi)型 , 一處是函數(shù)參數(shù)類(lèi)型 往核,返回值類(lèi)型沒(méi)什么可講的 聂儒, 在分析.h頭文件的時(shí)候 衩婚, 已經(jīng)詳細(xì)講述了 效斑。那 缓屠, 這兩個(gè)函數(shù)參數(shù)類(lèi)型jobject
與jclass
有什么區(qū)別呢 敌完? 這兩個(gè)類(lèi)型表示 滨溉, Java的native
函數(shù) 业踏, 是成員方法還是類(lèi)方法 勤家, 成員方法需要對(duì)象.方法名 伐脖, 類(lèi)方法則類(lèi)名.方法名 讼庇, 可以在main方法里面直接使用 近尚。
接下來(lái)是:
// 得到j(luò)class jclass就好比java的.class對(duì)象
jclass jcls = (*env)->GetObjectClass(env, jobj);
為什么要得到j(luò)class呢 ?
因?yàn)?和媳,我們要獲取字段ID 留瞳, 在JNI中 她倘, 獲取java字段與方法都需要簽名硬梁。而簽名是在類(lèi)加載的時(shí)候完成 靶溜, 所以在獲取字段ID的時(shí)候需要傳入jclass 罩息。
// 得到字段ID
jfieldID jfID = (*env)->GetFieldID(env, jcls, "name", "Ljava/lang/String;");
通過(guò)傳入jclass , 字段名稱(chēng) 瓷炮, 字段簽名 娘香, 就可以得到字段ID 烘绽,也可使用GetMethodID函數(shù)得到方法ID 俐填。
為什么傳入了字段名稱(chēng)英融,還需要簽名呢 驶悟?
因?yàn)閖ava支持重載 痕鳍, 一個(gè)方法名稱(chēng)可以有多個(gè)不同實(shí)現(xiàn) , 根據(jù)傳入的參數(shù)不同 恭应,所以C語(yǔ)言調(diào)用函數(shù)為了區(qū)分不同的方法昼榛, 而對(duì)每個(gè)方法做了簽名 胆屿, 而字段則可用來(lái)標(biāo)識(shí)類(lèi)型 (僅個(gè)人理解)非迹。
獲取字段與函數(shù)簽名的方式:
在.class的文件目錄下 憎兽,使用`javap -s -p className` 就可以列舉出 纯命, 所有的字段與方法簽名
// 得到字段的值 亿汞, 類(lèi)比java中的 對(duì)象.字段名得到值 疗我, 這里是字段的ID
jstring jstr = (*env)->GetObjectField(env, jobj, jfID);
// 將jstring類(lèi)型轉(zhuǎn)換成字符指針
char* cstr = (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
//printf("is vaule:%s\n", cstr);
// 拼接字符 char text[30] = " xiaojiu and "; strcat(text, cstr);
// 將字符指針轉(zhuǎn)換成jstring類(lèi)型
jstring new_str = (*env)->NewStringUTF(env, text);
因?yàn)閖ava類(lèi)型與C語(yǔ)言類(lèi)型不是相通的 南捂, 所有需要一個(gè)轉(zhuǎn)換 吴裤, 類(lèi)型的介紹在上一篇已經(jīng)詳細(xì)說(shuō)明 。
// 將jstring類(lèi)型的變量 溺健, 設(shè)置到j(luò)ava 字段中
// 類(lèi)比java中的 對(duì)象.字段名得到值 麦牺, 這里是字段的ID
(*env)->SetObjectField(env, jobj, jfID, new_str);
畫(huà)龍點(diǎn)睛:
上述中 , 我們?cè)L問(wèn)修改了String類(lèi)型的字段 矿瘦, 也基本上能看出訪問(wèn)字段的基本套路 枕面, 首先得到j(luò)class , 再得到字段ID 愿卒, 繼而得到字段的值 , 進(jìn)行類(lèi)型轉(zhuǎn)換 琼开, 最后將變化的值設(shè)置給Java字段 易结。由此可以推出 , 訪問(wèn)其他類(lèi)型的字段 , 也是這樣的套路 搞动, 只不過(guò)類(lèi)型變了 躏精, 值得注意的是 , java中的引用類(lèi)型是需要進(jìn)行類(lèi)型轉(zhuǎn)換的 鹦肿。
part 2 : C函數(shù)訪問(wèn)Java方法
一 矗烛, 定義Java 方法與調(diào)用方法的native方法
// C語(yǔ)言調(diào)用java方法
private native void accessJavaRandomNumberMethod() ;
// 調(diào)用
jni.accessJavaRandomNumberMethod() ;
二 , 在C語(yǔ)言頭文件中定義native方法的實(shí)現(xiàn)函數(shù) 箩溃, 并實(shí)現(xiàn)
// com.zeno.jni_HelloJNI.h
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaRandomNumberMethod
(JNIEnv *, jobject);
// Hello_JNI.c
// 訪問(wèn)java方法
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaRandomNumberMethod
(JNIEnv *env, jobject jobj) {
// 得到j(luò)class
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到方法ID
jmethodID jmtdId = (*env)->GetMethodID(env, jclazz, "getRandomNumber", "(I)I");
// 調(diào)用方法
jint jRandomNum = (*env)->CallIntMethod(env, jobj, jmtdId, 10);
// 打印
printf("得到j(luò)ava方法的隨機(jī)數(shù):%ld\n", jRandomNum);
}
三 瞭吃, 輸出
得到j(luò)ava方法的隨機(jī)數(shù):6
四 , 分析
不論是字段訪問(wèn)還是方法的調(diào)用 涣旨, 其基本的套路不變 歪架,調(diào)用Java方法與調(diào)用字段不同的是 ,
將得到字段ID改成得到方法ID 霹陡, 得到字段的值改成調(diào)用方法`CallXXX` 和蚪, 通過(guò)調(diào)用調(diào)用Java方法得到方法返回值 。
part 3 : C函數(shù)訪問(wèn)Java字段與方法(靜態(tài))
套路都是一樣的 烹棉, 這里僅給出代碼 攒霹, 不做詳細(xì)分析(本階段全部代碼) 。
/**
*
* @author Zeno
*
* JNI (Java Native Interface) java本地化接口
*
* Android Framework層與Native層相互通信的基石
*
*
*/
public class HelloJni {
// 使用C語(yǔ)言修改java字段
public String name = "zeno" ;
// 使用C語(yǔ)言修改java int 類(lèi)型字段
private int age = 20 ;
public static String flag = "flag1" ;
// 調(diào)用C語(yǔ)言函數(shù)方法
public static native String getStringFromC() ;
// 調(diào)用C++語(yǔ)言函數(shù)方法
public static native String getStringFromCPP() ;
// C語(yǔ)言修改java String 類(lèi)型字段本地方法
public native void accessJavaStringField() ;
// C語(yǔ)言修改java String static 類(lèi)型字段本地方法
public native void accessJavaStaticStringField() ;
// C語(yǔ)言修改java int 類(lèi)型字段本地方法
public native void accessJavaIntField() ;
// C語(yǔ)言調(diào)用java方法
private native void accessJavaRandomNumberMethod() ;
// 調(diào)用Java靜態(tài)方法
private native void accessJavaStaticMethod() ;
// 靜態(tài)native方法訪問(wèn)字段
private static native void staticAccessJavaField() ;
public static void main(String[] args) {
System.out.println("getStringFormC == "+getStringFromC());
System.out.println("getStringFormC == "+getStringFromCPP());
HelloJni jni = new HelloJni() ;
System.out.println("修改前 name 的值:"+jni.name);
//C語(yǔ)言修改java字段本地方法
jni.accessJavaStringField();
System.out.println("修改后 name 的值:"+jni.name);
System.out.println("修改前 flag 的值:"+flag);
//C語(yǔ)言修改java static 字段本地方法
jni.accessJavaStaticStringField();
System.out.println("修改后 flag 的值:"+flag);
System.out.println("修改前 age 的值:"+jni.age);
//C語(yǔ)言修改java字段本地方法
jni.accessJavaIntField();
System.out.println("修改后 age 的值:"+jni.age);
jni.accessJavaRandomNumberMethod() ;
jni.accessJavaStaticMethod() ;
// 靜態(tài)native方法 峦耘,訪問(wèn)java字段
System.out.println("修改前 flag 的值:"+flag);
staticAccessJavaField();
System.out.println("修改后 flag 的值:"+flag);
}
static{
// 加載動(dòng)態(tài)庫(kù)
System.loadLibrary("Hello_JNI") ;
}
// 調(diào)用java方法
private int getRandomNumber(int bound) {
return new Random().nextInt(bound) ;
}
// 調(diào)用java靜態(tài)方法
private static String getUUID() {
return UUID.randomUUID().toString();
}
}
C實(shí)現(xiàn) 剔蹋, 這里就不貼頭文件了 。
調(diào)用靜態(tài)的Java字段與方法 辅髓, 在C語(yǔ)言中調(diào)用相應(yīng)的static函數(shù) 泣崩, 例如:獲取靜態(tài)字段IDGetStaticFieldID
。
#define _CRT_SECURE_NO_WARNINGS
#include "com_zeno_jni_HelloJni.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/*
C/C++動(dòng)態(tài)庫(kù) 洛口, 在win平臺(tái)下以.dll文件標(biāo)識(shí) 矫付, 在linux下面以.so文件表示
在Android中 , 以.so文件表示 第焰, 因?yàn)锳ndroid使用的是linux內(nèi)核 买优。
*/
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC
(JNIEnv *Env, jclass jclazz) {
return (*Env)->NewStringUTF(Env, "Jni C String");
}
/*C語(yǔ)言訪問(wèn)java String類(lèi)型字段*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField
(JNIEnv *env, jobject jobj) {
// 得到j(luò)class , jclass就好比java的.class對(duì)象
jclass jcls = (*env)->GetObjectClass(env, jobj);
// 得到字段ID ,
jfieldID jfID = (*env)->GetFieldID(env, jcls, "name", "Ljava/lang/String;");
// 得到字段的值
jstring jstr = (*env)->GetObjectField(env, jobj, jfID);
// 將jstring類(lèi)型轉(zhuǎn)換成字符指針
char* cstr = (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
//printf("is vaule:%s\n", cstr);
// 拼接字符
char text[30] = " xiaojiu and ";
strcat(text, cstr);
//printf("modify value %s\n", text);
// 將字符指針轉(zhuǎn)換成jstring類(lèi)型
jstring new_str = (*env)->NewStringUTF(env, text);
// 將jstring類(lèi)型的變量 挺举, 設(shè)置到j(luò)ava 字段中
(*env)->SetObjectField(env, jobj, jfID, new_str);
}
/*C語(yǔ)言訪問(wèn)java int 類(lèi)型字段*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaIntField
(JNIEnv *env, jobject jobj) {
// 得到j(luò)class
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到字段ID
jfieldID jfid = (*env)->GetFieldID(env, jclazz, "age", "I");
// 得到字段值
jint jAge = (*env)->GetIntField(env, jobj, jfid);
jAge++;
(*env)->SetIntField(env, jobj, jfid, jAge);
}
// 訪問(wèn)java方法
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaRandomNumberMethod
(JNIEnv *env, jobject jobj) {
// 得到j(luò)class
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到方法ID
jmethodID jmtdId = (*env)->GetMethodID(env, jclazz, "getRandomNumber", "(I)I");
// 調(diào)用方法
jint jRandomNum = (*env)->CallIntMethod(env, jobj, jmtdId, 10);
// 打印
printf("得到j(luò)ava方法的隨機(jī)數(shù):%ld\n", jRandomNum);
}
// 訪問(wèn)java靜態(tài)字段
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStaticStringField
(JNIEnv *env, jobject jobj) {
// 得到j(luò)class
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到字段ID
jfieldID jfid = (*env)->GetStaticFieldID(env, jclazz, "flag", "Ljava/lang/String;");
// 得到字段的值
jobject jFLagStr = (*env)->GetStaticObjectField(env, jclazz, jfid);
// 將java字符串轉(zhuǎn)換成C字符指針
char* cFlagStr = (*env)->GetStringUTFChars(env, jFLagStr, JNI_FALSE);
//printf("is vaule:%s\n", cFlagStr);
char newStr[30] = " access static field ";
strcat(newStr, cFlagStr);
// 將C字符指針 杀赢, 轉(zhuǎn)換成java字符串
jstring jNewStr = (*env)->NewStringUTF(env, newStr);
// 將字符串設(shè)置到j(luò)ava字段上
(*env)->SetStaticObjectField(env, jclazz, jfid, jNewStr);
}
// 訪問(wèn)java靜態(tài)方法
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStaticMethod
(JNIEnv *env, jobject jobj) {
// 得到j(luò)class
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到靜態(tài)方法ID
jmethodID mtdid = (*env)->GetStaticMethodID(env, jclazz, "getUUID", "()Ljava/lang/String;");
// 調(diào)用靜態(tài)方法
jobject jUUIDStr = (*env)->CallStaticObjectMethod(env, jclazz, mtdid);
// 將java字符串轉(zhuǎn)換成C字符指針
char* cUUIDStr = (*env)->GetStringUTFChars(env, jUUIDStr, JNI_FALSE);
printf("is vaule:%s\n", cUUIDStr);
// 根據(jù)UUID生成臨時(shí)文件
char file_path[100] ;
sprintf(file_path, "e:\\dn\\%s.txt", cUUIDStr);
printf("is address:%s\n", file_path);
FILE* fp = fopen(file_path, "w");
if (fp == NULL) {
printf("文件創(chuàng)建失敗\n");
}
char* content = "落花有意流水無(wú)情";
// 寫(xiě)入內(nèi)容
fputs(content, fp);
// 關(guān)閉流
fclose(fp);
}
// 靜態(tài)native方法 , 訪問(wèn)java字段
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_staticAccessJavaField
(JNIEnv *env, jclass jclazz) {
// 得到字段ID
jfieldID jfid = (*env)->GetStaticFieldID(env, jclazz, "flag", "Ljava/lang/String;");
// 得到字段的值
jstring jflag = (*env)->GetStaticObjectField(env, jclazz, jfid);
// 將java字符串轉(zhuǎn)換成字符指針
char* cXj = (*env)->GetStringUTFChars(env, jflag, JNI_FALSE);
printf("is value:%s\n", cflag);
char newName[100] = "xiaojiu love ";
char* cNewName = strcat(newName, cflag);
// 將字符指針轉(zhuǎn)換成java類(lèi)型
jstring newStr = (*env)->NewStringUTF(env, cNewName);
// 設(shè)置
(*env)->SetStaticObjectField(env, jclazz, jfid, newStr);
}
編寫(xiě)套路
C語(yǔ)言訪問(wèn)Java語(yǔ)言的字段與方法 湘纵, 只要理解了一種 脂崔, 其他的都是套路 , 根據(jù)步驟一步一步來(lái)就可以了 梧喷。
步驟一 砌左、 得到
jclass
脖咐, 字節(jié)碼對(duì)象 , 如果是static native
修飾 汇歹, 則函數(shù)會(huì)以jclass
類(lèi)型傳入 屁擅, 非靜態(tài)則需要得到jclass
類(lèi)型 。
// 得到j(luò)class
jclass jclazz = (*env)->GetObjectClass(env, jobj);
步驟二 产弹、得到字段或方法ID , 區(qū)分靜態(tài)字段與對(duì)象字段 派歌, 靜態(tài)字段或方法調(diào)用
(*env)->GetStaticFieldID
得到靜態(tài)字段ID ,(*env)->GetStaticMethodID
得到靜態(tài)方法ID 痰哨, 對(duì)象字段調(diào)用(*env)->GetFieldID
得到字段ID硝皂,(*env)->GetMethodID
得到方法ID 。 可以得到一個(gè)套路 作谭, 靜態(tài)修飾的 稽物, 則調(diào)用static
標(biāo)識(shí)的函數(shù) , 非靜態(tài)的則調(diào)用常規(guī)函數(shù) 折欠。
// 得到字段ID 贝或, 對(duì)象字段
jfieldID jfid = (*env)->GetFieldID(env, jclazz, "age", "I");
// 得到字段ID , 靜態(tài)字段
jfieldID jfid = (*env)->GetStaticFieldID(env, jclazz, "flag", "Ljava/lang/String;");
步驟三 锐秦、 取得字段的值或調(diào)用方法 , 需要注意的是咪奖, 得到字段的值與調(diào)用方法 , 都有類(lèi)型的區(qū)分 酱床。引用類(lèi)型則使用
GetObjectField
羊赵,CallStaticObjectMethod
, 其他類(lèi)型 扇谣, 則有對(duì)于的jxxx類(lèi)型對(duì)應(yīng) 昧捷。套路簡(jiǎn)寫(xiě):Get<Type>Field
,GetStatic<Type>Field
罐寨,Call<Type>Method
靡挥,CallStatic<Type>Method
。
// 得到字段的值
jstring jstr = (*env)->GetObjectField(env, jobj, jfID);
// 得到字段值
jint jAge = (*env)->GetIntField(env, jobj, jfid);
// 調(diào)用方法
jint jRandomNum = (*env)->CallIntMethod(env, jobj, jmtdId, 10);
// 調(diào)用靜態(tài)方法
jobject jUUIDStr = (*env)->CallStaticObjectMethod(env, jclazz, mtdid);
步驟四 鸯绿、 類(lèi)型轉(zhuǎn)換 跋破, 如果是Java引用類(lèi)型 , 則需要進(jìn)行類(lèi)型轉(zhuǎn)換
// 將java字符串轉(zhuǎn)換成字符指針
char* cflag = (*env)->GetStringUTFChars(env, jflag, JNI_FALSE);
結(jié)語(yǔ)
真正的高手 瓶蝴, 不是樂(lè)而學(xué)得的 毒返, 真正的學(xué)習(xí) , 不是輕輕松松的 舷手。高手 拧簸, 需要刻意練習(xí) , 刻意練習(xí)不是重復(fù)相同的動(dòng)作 聚霜, 而是跳出舒適區(qū)熟悉區(qū)域 狡恬, 刻意練習(xí)自己不熟悉感覺(jué)艱難的事情 。感謝動(dòng)腦學(xué)院 蝎宇。
本文由老司機(jī)學(xué)院【動(dòng)腦學(xué)院】特約提供弟劲。
做一家受人尊敬的企業(yè),做一位令人尊敬的老師