JNI系列入門之C語言與Java的雙向通訊(一)

JNI系列文章:java

  1. JNI系列之入門Hello JNI C(一)
  2. JNI系列之入門Hello JNI C(二)
  3. JNI系列入門之C語言與Java的雙向通訊(一)
  4. JNI系列入門之C語言與Java的雙向通訊(二)
  5. JNI系列入門之C語言中文字符串亂碼問題

1、概述

  1. Java層向C層通訊
  • 經過調用靜態無參數、有參數的native方法
  • 經過調用非靜態無參數、有參數的native方法
  1. C層向Java層通訊
  • C層訪問和修改Java層的屬性
  • C層訪問和修改Java層的靜態屬性
  • C層訪問Java層的方法
  • C層訪問Java層的靜態方法
  • C層訪問Java層的構造方法,並建立Java對象返回
  • java中傳入數組
  • C中生成一個數組返回給java

2、實現

Java層向C層通訊數組

  • 經過調用靜態無參數、有參數的native方法
JniTest.java

// native的靜態方法,生成的JNI函數參數是(JNIEnv *env, jclass jcls)
public native static String getStringFromC();
// 生成的JNI函數參數是(JNIEnv *env, jclass jcls, jstring jstr_input)
public native static String getNewString(String input);
複製代碼

在java中聲明兩個native方法,而後經過javah命令生成頭文件,具體的頭文件生成步驟,能夠看JNI系列之入門Hello JNI C(一)函數

com_jerry_jnitest_JniTest.h JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getStringFromC (JNIEnv *, jclass);

JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getNewString (JNIEnv *, jclass, jstring);
複製代碼

具體實現:post

// 靜態無參函數實現,返回一個C的字符串
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getStringFromC (JNIEnv *env, jclass jcls) {
	char *text = "Hi, Jerry! 動態連接庫,調用起來了!";
	return (*env)->NewStringUTF(env, text);
}

// 靜態有參函數實現,新建一個C字符串與java輸入的字符串拼接後返回給java
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getNewString (JNIEnv *env, jclass jcls, jstring jstr_input) {
        // 將java的輸入參數jstring -> c字符串
	char *input = (*env)->GetStringUTFChars(env, jstr_input, NULL);
	char text[30] = "Jerry";
	// 拼接字符串
	char *new_text = strcat(text, input);
	printf("newText = %s\n", new_text);
	// 建立一個jstring返回給java
	jstring jstr_new = (*env)->NewStringUTF(env, new_text);
	return jstr_new;
}
複製代碼

getStringFromC函數中,咱們使用(*env)來調用NewStringUTF函數經過傳入C字符指針也就是字符串來建立一個jstring類型的字符串(對應java數據類型的String),很簡單。也許你會問,爲何是(*env)來調用函數,由於JNIEnv自己就是一個結構體指針了,因此env就是結構體二級指針變量,而NewStringUTF函數又是定義在JNIEnv這個結構體指針的結構體裏的函數(這是一個函數指針),因此須要用(*env)來取到結構體一級指針變量的地址,來獲取其地址所指向的內容。具體能夠看上一篇文章JNI系列之入門Hello JNI C(二) getNewString函數中多了一個參數,jstr_input表示java中native方法的參數String input,在函數實現裏,先將jstring轉換成c字符串,而後引入c的函數庫string.h頭文件,使用strcat拼接出一個新的c字符串,而後再用NewStringUTF函數把新的C字符串->jstring,返回給java。spa

public static void main(String[] args) {
		// 在main方法中調用native方法
		System.out.println(getStringFromC());
		System.out.println(getNewString("真是太帥了..."));
}
複製代碼

輸出結果: 指針

Paste_Image.png
這是java中的native方法調用,看的出來,C的中文字符串亂碼,而java傳入的中文字符串不會亂碼。關於C的中文字符串亂碼問題,我將會在下一篇文章裏說明解決方案。

  • 經過調用非靜態無參數、有參數的native方法
// native的對象方法,生成的NI函數參數是(JNIEnv *env, jobject jobj)
	public native String getNameFromC();
複製代碼

C中的頭文件實現:code

// 非靜態無參函數
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getNameFromC (JNIEnv *env, jobject jobj) {
	
	return (*env)->NewStringUTF(env, "call getNameFromC");
}
複製代碼

對於非靜態native方法,就是函數的參數變成了jobject,這個jobj就是在java中調用非靜態native方法的那個對象,好比:cdn

JniTest  jt = new JniTest();
jt.getNameFromC();
複製代碼

jobj就表示對象jt。而靜態的jclass就表示native方法聲明的那個類類型好比JniTest.class。對象

C層向Java層通訊blog

  • C層訪問和修改Java層的屬性 咱們在java中定義一個屬性name:
public String name = "Jerry";
複製代碼

爲了方便編寫使用C層來調用Java層的內容,接下來都會先在Java層建立一個native方法,而後在C層native函數實現裏來調用訪問Java層的內容。

/** * @return 修改後的屬性內容 */
	public native String accessField();
複製代碼

C中的實現:

// 1. 訪問java非屬性
// 修改java中屬性name的值並返回
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_accessField (JNIEnv *env, jobject jobj) {

	// 獲取java中name屬性所在對象的class類類型
	jclass jcls = (*env)->GetObjectClass(env, jobj);

	// 獲取name屬性的fieldID
	// (參數:name表示Java中屬性的名字,最後一個參數表示屬性的類型簽名)
	jfieldID fid = (*env)->GetFieldID(env, jcls, "name", "Ljava/lang/String;");
	if (fid == NULL) {
		printf("fid is NULL!");
	}
	
	// 經過屬性的fieldId獲取屬性的值
	jstring jstr_fvalue = (*env)->GetObjectField(env, jobj, fid);

	// 要想操做修改屬性值,得先把jstring -> 轉換成 c的字符串
	// 最後一個參數:JNI_TRUE是一個宏定義值是1,表示true須要拷貝(拷貝過一分內存地址),
    // JNI_FALSE表示不須要拷貝(和java使用同一份字符串內存地址),官方建議使用NULL
	char *ch_fvalue = (*env)->GetStringUTFChars(env, jstr_fvalue, NULL);

	// 使用string.h的字符串操做庫修改屬性值
	char text[20] = "hello ";
	char *new_fvalue = strcat(text, ch_fvalue);
	// c -> java
	jstring new_jstr = (*env)->NewStringUTF(env, new_fvalue);
	// 修改屬性的值
	(*env)->SetObjectField(env, jobj, fid, new_jstr);

	return new_jstr;
}
複製代碼

代碼中註釋寫的很清楚了,這裏還須要注意的是關於屬性和方法的簽名規則:

官方文檔
官方文檔

數據類型 簽名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
Object L開頭,而後以/分隔包的完整類型名,後面再加";"(分號),好比說 String 簽名就是Ljava/lang/String;
int[]
Object[]
以[開頭,在加上數組元素類型的簽名,好比int[] 簽名就是[I , 再好比 int[][] 簽名就是 [[I ,object數組簽名就是 [Ljava/lang/Object;
  • C層訪問和修改Java層的靜態屬性

在java中定義一個靜態屬性size:

private static int size = 26;
複製代碼

在C中實現一個函數用來修改java的這個靜態屬性:

// 2. 訪問java靜態屬性
// 修改java中靜態屬性size的值並返回
JNIEXPORT void JNICALL Java_com_jerry_jnitest_JniTest_accessStaticField (JNIEnv *env, jobject jobj) {

	// 獲取jclass
	jclass cls = (*env)->GetObjectClass(env, jobj);
	// 獲取jfieldID
	jfieldID fID = (*env)->GetStaticFieldID(env, cls, "size", "I");
	// 獲取屬性值
	jint size= (*env)->GetStaticIntField(env, cls, fID);
	size += 12;
	// 修改屬性值
	(*env)->SetStaticIntField(env, cls, fID, size);
}
複製代碼

通過上面非靜態屬性的訪問,靜態的屬性就很簡單了,同樣的套路:獲取jclass -> 獲取屬性的jfieldID -> 獲取屬性值 ->設置屬性值。還有一個套路:獲取屬性值 通常都是GetStaticField,設置屬性值通常都是SetStaticField。


JNI系列文章:

  1. JNI系列之入門Hello JNI C(一)
  2. JNI系列之入門Hello JNI C(二)
  3. JNI系列入門之C語言與Java的雙向通訊(一)
  4. JNI系列入門之C語言與Java的雙向通訊(二)
  5. JNI系列入門之C語言中文字符串亂碼問題
相關文章
相關標籤/搜索