JNI開發系列③C語言呼叫Java欄位與方法
接續上篇JNI開發系列②.h標頭檔案分析
前情提要
在前面 , 我們已經熟悉了JNI的開發流程 , .h標頭檔案的分析 , 生成標頭檔案javah
命令 , 以及java型別在C語言中的表現形式 , 值得注意的是 , java中的所有引用型別都是jobject
型別 , native
生成的函式 , 以Java_全類名_方法名
表示,包名的.
以_
表示 。
概述
在開篇的時候 ,我們就使用java的native
方法呼叫過C函式 , 返回了一個String型別的字串 , 使用(*Env)->NewStringUTF(Env, "Jni C String");
函式 , 我們將字元指標轉換成jstring
, java型別的字串返回給了我們的java層 。今天我們來學習 , 使用C語言來呼叫Java的欄位與方法 。
part 1 : C 函式訪問java欄位
一 , 定義Java 的
String
型別欄位與修改欄位的native
方法
// 使用C語言修改java欄位
public String name = "zeno" ;
// C語言修改java String 型別欄位本地方法
public native void accessJavaStringField() ;
// 呼叫
HelloJni jni = new HelloJni() ;
System.out.println("修改前 name 的值:"+jni.name);
//C語言修改java欄位本地方法
jni.accessJavaStringField();
System.out.println("修改後 name 的值:"+jni.name);
二 , 在C語言標頭檔案中定義
native
方法的實現函式 , 並實現
// com.zeno.jni_HelloJNI.h
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField
(JNIEnv *, jobject);
// Hello_JNI.c
/*C語言訪問java String型別欄位*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField
(JNIEnv *env, jobject jobj) {
// 得到jclass
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型別轉換成字元指標
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);
// 將字元指標轉換成jstring型別
jstring new_str = (*env)->NewStringUTF(env, text);
// 將jstring型別的變數 , 設定到java 欄位中
(*env)->SetObjectField(env, jobj, jfID, new_str);
}
三 , 輸出
修改前 name 的值:zeno
修改後 name 的值: xiaojiu and zeno
四 , 分析
首先來分析C函式:
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)
我們可以看出兩處不同 , 一處是返回值型別 , 一處是函式引數型別 ,返回值型別沒什麼可講的 , 在分析.h標頭檔案的時候 , 已經詳細講述了 。那 , 這兩個函式引數型別jobject
與jclass
有什麼區別呢 ? 這兩個型別表示 , Java的native
函式 , 是成員方法還是類方法 , 成員方法需要物件.方法名 , 類方法則類名.方法名 , 可以在main方法裡面直接使用 。
接下來是:
// 得到jclass jclass就好比java的.class物件
jclass jcls = (*env)->GetObjectClass(env, jobj);
為什麼要得到jclass呢 ?
因為 ,我們要獲取欄位ID , 在JNI中 , 獲取java欄位與方法都需要簽名。而簽名是在類載入的時候完成 , 所以在獲取欄位ID的時候需要傳入jclass 。
// 得到欄位ID
jfieldID jfID = (*env)->GetFieldID(env, jcls, "name", "Ljava/lang/String;");
通過傳入jclass , 欄位名稱 , 欄位簽名 , 就可以得到欄位ID ,也可使用GetMethodID函式得到方法ID 。
為什麼傳入了欄位名稱,還需要簽名呢 ?
因為java支援過載 , 一個方法名稱可以有多個不同實現 , 根據傳入的引數不同 ,所以C語言呼叫函式為了區分不同的方法, 而對每個方法做了簽名 , 而欄位則可用來標識型別 (僅個人理解)。
獲取欄位與函式簽名的方式:
在.class的檔案目錄下 ,使用`javap -s -p className` 就可以列舉出 , 所有的欄位與方法簽名
// 得到欄位的值 , 類比java中的 物件.欄位名得到值 , 這裡是欄位的ID
jstring jstr = (*env)->GetObjectField(env, jobj, jfID);
// 將jstring型別轉換成字元指標
char* cstr = (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
//printf("is vaule:%s\n", cstr);
// 拼接字元 char text[30] = " xiaojiu and "; strcat(text, cstr);
// 將字元指標轉換成jstring型別
jstring new_str = (*env)->NewStringUTF(env, text);
因為java型別與C語言型別不是相通的 , 所有需要一個轉換 , 型別的介紹在上一篇已經詳細說明 。
// 將jstring型別的變數 , 設定到java 欄位中
// 類比java中的 物件.欄位名得到值 , 這裡是欄位的ID
(*env)->SetObjectField(env, jobj, jfID, new_str);
畫龍點睛:
上述中 , 我們訪問修改了String型別的欄位 , 也基本上能看出訪問欄位的基本套路 , 首先得到jclass , 再得到欄位ID , 繼而得到欄位的值 , 進行型別轉換 , 最後將變化的值設定給Java欄位 。由此可以推出 , 訪問其他型別的欄位 , 也是這樣的套路 , 只不過型別變了 , 值得注意的是 , java中的引用型別是需要進行型別轉換的 。
part 2 : C函式訪問Java方法
一 , 定義Java 方法與呼叫方法的native方法
// C語言呼叫java方法
private native void accessJavaRandomNumberMethod() ;
// 呼叫
jni.accessJavaRandomNumberMethod() ;
二 , 在C語言標頭檔案中定義native方法的實現函式 , 並實現
// com.zeno.jni_HelloJNI.h
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaRandomNumberMethod
(JNIEnv *, jobject);
// Hello_JNI.c
// 訪問java方法
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaRandomNumberMethod
(JNIEnv *env, jobject jobj) {
// 得到jclass
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到方法ID
jmethodID jmtdId = (*env)->GetMethodID(env, jclazz, "getRandomNumber", "(I)I");
// 呼叫方法
jint jRandomNum = (*env)->CallIntMethod(env, jobj, jmtdId, 10);
// 列印
printf("得到java方法的隨機數:%ld\n", jRandomNum);
}
三 , 輸出
得到java方法的隨機數:6
四 , 分析
不論是欄位訪問還是方法的呼叫 , 其基本的套路不變 ,呼叫Java方法與呼叫欄位不同的是 ,
將得到欄位ID改成得到方法ID , 得到欄位的值改成呼叫方法`CallXXX` , 通過呼叫呼叫Java方法得到方法返回值 。
part 3 : C函式訪問Java欄位與方法(靜態)
套路都是一樣的 , 這裡僅給出程式碼 , 不做詳細分析(本階段全部程式碼) 。
/**
*
* @author Zeno
*
* JNI (Java Native Interface) java本地化介面
*
* Android Framework層與Native層相互通訊的基石
*
*
*/
public class HelloJni {
// 使用C語言修改java欄位
public String name = "zeno" ;
// 使用C語言修改java int 型別欄位
private int age = 20 ;
public static String flag = "flag1" ;
// 呼叫C語言函式方法
public static native String getStringFromC() ;
// 呼叫C++語言函式方法
public static native String getStringFromCPP() ;
// C語言修改java String 型別欄位本地方法
public native void accessJavaStringField() ;
// C語言修改java String static 型別欄位本地方法
public native void accessJavaStaticStringField() ;
// C語言修改java int 型別欄位本地方法
public native void accessJavaIntField() ;
// C語言呼叫java方法
private native void accessJavaRandomNumberMethod() ;
// 呼叫Java靜態方法
private native void accessJavaStaticMethod() ;
// 靜態native方法訪問欄位
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語言修改java欄位本地方法
jni.accessJavaStringField();
System.out.println("修改後 name 的值:"+jni.name);
System.out.println("修改前 flag 的值:"+flag);
//C語言修改java static 欄位本地方法
jni.accessJavaStaticStringField();
System.out.println("修改後 flag 的值:"+flag);
System.out.println("修改前 age 的值:"+jni.age);
//C語言修改java欄位本地方法
jni.accessJavaIntField();
System.out.println("修改後 age 的值:"+jni.age);
jni.accessJavaRandomNumberMethod() ;
jni.accessJavaStaticMethod() ;
// 靜態native方法 ,訪問java欄位
System.out.println("修改前 flag 的值:"+flag);
staticAccessJavaField();
System.out.println("修改後 flag 的值:"+flag);
}
static{
// 載入動態庫
System.loadLibrary("Hello_JNI") ;
}
// 呼叫java方法
private int getRandomNumber(int bound) {
return new Random().nextInt(bound) ;
}
// 呼叫java靜態方法
private static String getUUID() {
return UUID.randomUUID().toString();
}
}
C實現 , 這裡就不貼標頭檔案了 。
呼叫靜態的Java欄位與方法 , 在C語言中呼叫相應的static函式 , 例如:獲取靜態欄位IDGetStaticFieldID
。
#define _CRT_SECURE_NO_WARNINGS
#include "com_zeno_jni_HelloJni.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/*
C/C++動態庫 , 在win平臺下以.dll檔案標識 , 在linux下面以.so檔案表示
在Android中 , 以.so檔案表示 , 因為Android使用的是linux核心 。
*/
/*
* 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語言訪問java String型別欄位*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField
(JNIEnv *env, jobject jobj) {
// 得到jclass , jclass就好比java的.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型別轉換成字元指標
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);
// 將字元指標轉換成jstring型別
jstring new_str = (*env)->NewStringUTF(env, text);
// 將jstring型別的變數 , 設定到java 欄位中
(*env)->SetObjectField(env, jobj, jfID, new_str);
}
/*C語言訪問java int 型別欄位*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaIntField
(JNIEnv *env, jobject jobj) {
// 得到jclass
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);
}
// 訪問java方法
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaRandomNumberMethod
(JNIEnv *env, jobject jobj) {
// 得到jclass
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到方法ID
jmethodID jmtdId = (*env)->GetMethodID(env, jclazz, "getRandomNumber", "(I)I");
// 呼叫方法
jint jRandomNum = (*env)->CallIntMethod(env, jobj, jmtdId, 10);
// 列印
printf("得到java方法的隨機數:%ld\n", jRandomNum);
}
// 訪問java靜態欄位
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStaticStringField
(JNIEnv *env, jobject jobj) {
// 得到jclass
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字串轉換成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字元指標 , 轉換成java字串
jstring jNewStr = (*env)->NewStringUTF(env, newStr);
// 將字串設定到java欄位上
(*env)->SetStaticObjectField(env, jclazz, jfid, jNewStr);
}
// 訪問java靜態方法
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJni_accessJavaStaticMethod
(JNIEnv *env, jobject jobj) {
// 得到jclass
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// 得到靜態方法ID
jmethodID mtdid = (*env)->GetStaticMethodID(env, jclazz, "getUUID", "()Ljava/lang/String;");
// 呼叫靜態方法
jobject jUUIDStr = (*env)->CallStaticObjectMethod(env, jclazz, mtdid);
// 將java字串轉換成C字元指標
char* cUUIDStr = (*env)->GetStringUTFChars(env, jUUIDStr, JNI_FALSE);
printf("is vaule:%s\n", cUUIDStr);
// 根據UUID生成臨時檔案
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("檔案建立失敗\n");
}
char* content = "落花有意流水無情";
// 寫入內容
fputs(content, fp);
// 關閉流
fclose(fp);
}
// 靜態native方法 , 訪問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字串轉換成字元指標
char* cXj = (*env)->GetStringUTFChars(env, jflag, JNI_FALSE);
printf("is value:%s\n", cflag);
char newName[100] = "xiaojiu love ";
char* cNewName = strcat(newName, cflag);
// 將字元指標轉換成java型別
jstring newStr = (*env)->NewStringUTF(env, cNewName);
// 設定
(*env)->SetStaticObjectField(env, jclazz, jfid, newStr);
}
編寫套路
C語言訪問Java語言的欄位與方法 , 只要理解了一種 , 其他的都是套路 , 根據步驟一步一步來就可以了 。
步驟一 、 得到
jclass
, 位元組碼物件 , 如果是static native
修飾 , 則函式會以jclass
型別傳入 , 非靜態則需要得到jclass
型別 。
// 得到jclass
jclass jclazz = (*env)->GetObjectClass(env, jobj);
步驟二 、得到欄位或方法ID , 區分靜態欄位與物件欄位 , 靜態欄位或方法呼叫
(*env)->GetStaticFieldID
得到靜態欄位ID ,(*env)->GetStaticMethodID
得到靜態方法ID , 物件欄位呼叫(*env)->GetFieldID
得到欄位ID,(*env)->GetMethodID
得到方法ID 。 可以得到一個套路 , 靜態修飾的 , 則呼叫static
標識的函式 , 非靜態的則呼叫常規函式 。
// 得到欄位ID , 物件欄位
jfieldID jfid = (*env)->GetFieldID(env, jclazz, "age", "I");
// 得到欄位ID , 靜態欄位
jfieldID jfid = (*env)->GetStaticFieldID(env, jclazz, "flag", "Ljava/lang/String;");
步驟三 、 取得欄位的值或呼叫方法 , 需要注意的是, 得到欄位的值與呼叫方法 , 都有型別的區分 。引用型別則使用
GetObjectField
,CallStaticObjectMethod
, 其他型別 , 則有對於的jxxx型別對應 。套路簡寫: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);
// 呼叫方法
jint jRandomNum = (*env)->CallIntMethod(env, jobj, jmtdId, 10);
// 呼叫靜態方法
jobject jUUIDStr = (*env)->CallStaticObjectMethod(env, jclazz, mtdid);
步驟四 、 型別轉換 , 如果是Java引用型別 , 則需要進行型別轉換
// 將java字串轉換成字元指標
char* cflag = (*env)->GetStringUTFChars(env, jflag, JNI_FALSE);
結語
真正的高手 , 不是樂而學得的 , 真正的學習 , 不是輕輕鬆鬆的 。高手 , 需要刻意練習 , 刻意練習不是重複相同的動作 , 而是跳出舒適區熟悉區域 , 刻意練習自己不熟悉感覺艱難的事情 。感謝動腦學院 。
本文由老司機學院【動腦學院】特約提供。
做一家受人尊敬的企業,做一位令人尊敬的老師
相關文章
- JAVA JNI 呼叫C語言 DemoJavaC語言
- C語言 JNI 呼叫JAVA DemoC語言Java
- Android JNI開發系列之Java與C相互呼叫AndroidJava
- Java如何呼叫C語言程式,JNI技術JavaC語言
- java開發C語言編譯器:為C語言提供API呼叫JavaC語言編譯API
- Android Studio NDK開發-JNI呼叫Java方法AndroidJava
- go語言與c語言的相互呼叫GoC語言
- C語言呼叫 Java(Linux)C語言JavaLinux
- java呼叫c++動態庫之jni呼叫JavaC++
- Android JNI實現Java與C/C++互相呼叫,以及so庫的生成和呼叫(JNI方式呼叫美圖秀秀so)AndroidJavaC++
- Android Studio NDK開發:JNI呼叫Java函式AndroidJava函式
- JNI/NDK開發指南(6):C/C++訪問Java例項方法和靜態方法C++Java
- C語言開發工具C語言
- C語言位操作C語言
- Java 語言概述與開發環境(1)Java開發環境
- Java 語言概述與開發環境(2)Java開發環境
- 【開發語言】PHP、Java、C語言的編譯執行過程PHPJavaC語言編譯
- C語言系列之 寬字串與 C 語言在開發中的運用-尹成-專題視訊課程C語言字串
- android實現app通過jni呼叫C/C++方法AndroidAPPC++
- C++庫封裝JNI介面——實現java呼叫c++C++封裝Java
- C語言進位制轉換與列印C語言
- JNI/NDK開發指南(8):呼叫構造方法和父類例項方法構造方法
- Java與C語言的區別?JavaC語言
- 使用 Rust 語言編寫 Java JNI 實現RustJava
- 【Go 語言入門專欄】Go 語言的起源與發展Go
- Java語言與C++語言的差異總結JavaC++
- C語言---“C語言 誰與爭鋒?”C語言
- Java開發之路—java語言概述Java
- JNI/NDK開發指南(9):JNI呼叫效能測試及優化優化
- C語言函式呼叫棧C語言函式
- C語言位運算C語言
- 在InfoPakcage 中消失的語言欄位
- Android JNI開發系列之配置Android
- 二進位制列印與逆序_C語言(轉)C語言
- C#屬性與欄位C#
- android使用JNI呼叫C,C++程式AndroidC++
- MediaScanner Java, JNI, mediaservice 呼叫關係Java
- java開發系統核心:使用C語言開發系統應用程式JavaC語言