jni java_JNI 常见用法
一、Java 代碼 和JNI代碼通信
Java代碼通過JNI接口 調用 C/C++方法
1、首先我們需要在Java代碼中聲明Natvie方法原型
public native void helloJNI(String msg);
2、其次我們需要在C/C++代碼里聲明JNI方法的原型
如:
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv* env, jobject thiz,jstring msg) {
//do something
}
extern "C"。JNI函數聲明聲明代碼是用C++語言寫的,所以需要添加extern "C"聲明;如果源代碼是C語言聲明,則不需要添加這個聲明
JNIEXPORT。這個關鍵字表明這個函數是一個可導出函數。每一個C/C++庫都有一個導出函數列表,只有在這個列表里面的函數才可以被外部直接調用,類似Java的public函數和private函數的區別。
JNICALL。說明這個函數是一個JNI函數,用來和普通的C/C++函數進行區別。
Void 返回值類型
JNI函數名原型:Java_ + JNI方法所在的完整的類名,把類名里面的”.”替換成”_” + 真實的JNI方法名,這個方法名要和Java代碼里面聲明的JNI方法名一樣。
env 參數 是一個執行JNIENV函數表的指針。
thiz 參數 代表的是聲明這個JNI方法的Java類的引用。
msg 參數就是和Java聲明的JNI函數的msg參數對于的JNI函數參數
靜態JNI方法 和實例JNI方法的區別
Java代碼:
public native void showHello();
public native static void showHello2();
C++代碼:
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_showHello(JNIEnv* env, jobject thiz) {
//do something
}
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_showHello2(JNIEnv* env, jclass thiz) {
//do something
}
二、java 和JNI類型對照表
Java 和JNI基本類型對照表
java的基本類型可以直接與C/C++的基本類型映射。
image
Java與JNI引用類型對照表
與Java基本類型不同,引用類型對開發人員是不透明的。Java內部數據結構并不直接向原生代碼開放。也就是說 C/C++代碼并不能直接訪問Java代碼的字段和方法
image
三、JNI 基本操作舉例
1、JNI操作 字符串
java 類 TestNatvie.java
/**
* 字符串相關測試代碼
* @param str
*/
public native void testJstring(String str);
C++文件 natvie-lib.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testJstring(JNIEnv *env, jobject instance,
jstring str_) {
//(1)生成JNI String
char const * str = "hello world!";
jstring jstring = env->NewStringUTF(str);
// (2) jstring 轉換成 const char * charstr
const char *charstr = env->GetStringUTFChars(str_, 0);
// (3) 釋放 const char *
env->ReleaseStringUTFChars(str_, charstr);
//(4) 獲取字符串子集
char * subStr = new char;
env->GetStringUTFRegion(str_,0,3,subStr);//截取字符串char*;
env->ReleaseStringUTFChars(str_, subStr);
}
2、JNI操作數組
java 類 TestNatvie.java
/**
* 整形數組相關代碼
* @param array
*/
public native void testIntArray(int []array);
/**
*
* Object Array 相關測試 代碼
* @param strArr
*/
public native void testObjectArray(String[]strArr);
C++文件 natvie-lib.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testIntArray(JNIEnv *env, jobject instance,
jintArray array_) {
//----獲取數組元素
//(1)獲取數組中元素
jint * intArray = env->GetIntArrayElements(array_,NULL);
int len = env->GetArrayLength(array_);//(2)獲取數組長度
LOGD("feifei len:%d",len);
for(int i = 0; i < len;i++){
jint item = intArray[i];
LOGD("feifei item[%d]:%d",i,item);
}
env->ReleaseIntArrayElements(array_, intArray, 0);
//----- 獲取子數組
jint *subArray = new jint;
env->GetIntArrayRegion(array_,0,3,subArray);
for(int i = 0;i<3;i++){
subArray[i]= subArray[i]+5;
LOGD("feifei subArray:[%d]:",subArray[i]);
}
//用子數組修改原數組元素
env->SetIntArrayRegion(array_,0,3,subArray);
env->ReleaseIntArrayElements(array_,subArray,0);//釋放子數組元素
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testObjectArray(JNIEnv *env, jobject instance,
jobjectArray strArr) {
//獲取數組長度
int len = env->GetArrayLength(strArr);
for(int i = 0;i< len;i++){
//獲取Object數組元素
jstring item = (jstring)env->GetObjectArrayElement(strArr,i);
const char * charStr = env->GetStringUTFChars(item, false);
LOGD("feifei strArray item:%s",charStr);
jstring jresult = env->NewStringUTF("HaHa");
//設置Object數組元素
env->SetObjectArrayElement(strArr,i,jresult);
env->ReleaseStringUTFChars(item,charStr);
}
}
3、JNI 訪問Java類的方法和字段
JNI 中訪問java類的方法和字段都是 通過反射來實現的。
JNI獲取Java類的方法ID和字段ID,都需要一個很重要的參數,就是Java類的方法和字段的簽名
image
JNI 中訪問Java對象的屬性 和方法:
java 類 TestNatvie.java
public class TestNatvie {
static {
System.loadLibrary("native-lib");
}
/**
* Jni調用 java 對象方法
*/
public native void testCallJavaMethod();
/**
* Jni 調用 java static 方法
*/
public native void testCallStaticJavaMethod();
/**
* JNI 訪問 java 的對象屬性和類屬性
* @param student
*/
public native void getJavaObjectField(Student student);
public void helloworld(String msg){
Log.d("feifei","hello world:"+msg);
}
public static void helloworldStatic(String msg){
Log.d("feifei","hello world:"+msg);
}
}
C++ 類 natvie-lib.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testCallJavaMethod(JNIEnv *env, jobject instance) {
//獲取類名
jclass clazz = env->GetObjectClass(instance);
if(clazz == NULL) return;
jmethodID javaMethod = env->GetMethodID(clazz,"helloworld","(Ljava/lang/String;)V");
if(javaMethod == NULL)return;
const char * msg = "nancy";
jstring jmsg = env->NewStringUTF(msg);
env->CallVoidMethod(instance,javaMethod,jmsg);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testCallStaticJavaMethod(JNIEnv *env, jobject instance) {
//獲取java類型
jclass clazz = env->GetObjectClass(instance);
if(clazz == NULL) return;
jmethodID staticMethod = env->GetStaticMethodID(clazz,"helloworldStatic","(Ljava/lang/String;)V");
if(staticMethod == NULL) return;
jstring jmsg = env->NewStringUTF("wangfeng");
env->CallStaticVoidMethod(clazz,staticMethod,jmsg);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_getJavaObjectField(JNIEnv *env, jobject instance,
jobject student) {
jclass clazz = env->GetObjectClass(student);
if(clazz == NULL )return;
// 獲取Object 實例屬性
jfieldID nameId = env->GetFieldID(clazz,"name","Ljava/lang/String;");
jstring jname = (jstring)env->GetObjectField(student,nameId);
jfieldID ageId = env->GetFieldID(clazz,"age","I");
jint jage = env->GetIntField(student,ageId);
const char * name = env->GetStringUTFChars(jname,false);
env->ReleaseStringUTFChars(jname,name);
//獲取java 類屬性:
jfieldID gradeId = env->GetStaticFieldID(clazz,"grade","I");
jint jgrade = env->GetStaticIntField(clazz,gradeId);
jfieldID nickeNameID = env->GetStaticFieldID(clazz,"nickname","Ljava/lang/String;");
jstring jnickname = (jstring)env->GetStaticObjectField(clazz,nickeNameID);
const char * nickeName = env->GetStringUTFChars(jnickname, false);
env->ReleaseStringUTFChars(jnickname,nickeName);
LOGD("feifei name:%s,age:%d,grade:%d,nickname:%s",name,jage,jgrade,nickeName);
//JNI 設置 java對象屬性
env->SetObjectField(student,nameId,env->NewStringUTF("張三"));
//JNI 設置 java 類屬性
env->SetStaticObjectField(clazz,nickeNameID,env->NewStringUTF("小白"));
jstring jnameNew = (jstring)env->GetObjectField(student,nameId);
jstring jnickNameNew = (jstring)env->GetStaticObjectField(clazz,nickeNameID);
const char * newName = env->GetStringUTFChars(jnameNew, false);
const char *newNickName = env->GetStringUTFChars(jnickNameNew, false);
env->ReleaseStringUTFChars(jnameNew,newName);
env->ReleaseStringUTFChars(jnickNameNew,newName);
LOGD("feifei after update name:%s,age:%d,grade:%d,nickname:%s",newName,jage,jgrade,newNickName);
}
4、JNI對象的全局引用和局部引用
Java代碼的內存是由垃圾回收器來管理,而JNI代碼則不受Java的垃圾回收器來管理。所以JNI代碼提供了一組函數,來管理通過JNI代碼生成的JNI對象,比如jobject,jclass,jstring,jarray等。
JNI對象的局部引用
在JNI接口函數中引用JNI對象的局部變量,都是對JNI對象的局部引用,一旦JNI接口函數返回,所有這些JNI對象都會被自動釋放。
不過我們也可以采用JNI代碼提供的DeleteLocalRef函數來刪除一個局部JNI對象引用。
//聲明局部變量clazz
jclass clazz = env->GetObjectClass(instance);
//手動釋放 局部變量 clazz ;DeleteLocalRef 也可不用手動調用,JNI方法返回之后,會自動釋放局部JNI變量
env->DeleteLocalRef(clazz);
JNI對象的全局引用
JNI對象的全局引用分為兩種,一種是強全局引用,這種引用會阻止Java的垃圾回收器回收JNI代碼引用的Java對象,另一種是弱全局引用,這種全局引用則不會阻止垃圾回收器回收JNI代碼引用的Java對象。
1、強全局引用
NewGlobalRef用來創建強全局引用的JNI對象
DeleteGlobalRef用來刪除強全局引用的JNI對象
2、弱全局引用
NewWeakGlobalRef用來創建弱全局引用的JNI對象
DeleteWeakGlobalRef用來刪除弱全局引用的JNI對象
IsSameObject用來判斷兩個JNI對象是否相同
Java類 TestNatvie.java
/**
* 測試 JNI 強全局引用 和弱全局引用
*/
public native void testJNIReference(Object object);
C++ 代碼 natvie-lib.cpp
/**
* (1)在JNI接口函數中引用JNI對象的局部變量,都是對JNI對象的局部引用,一旦JNI接口函數返回,所有這些JNI對象都會被自動釋放。不過我們也可以采用JNI代碼提供的DeleteLocalRef函數來刪除一個局部JNI對象引用
* (2)對于JNI對象,絕對不能簡單的聲明一個全局變量,在JNI接口函數里面給這個全局變量賦值這么簡單,一定要使用JNI代碼提供的管理JNI對象的函數.
* JNI 全局引用分為兩種: 一種全局引用,這種引用會阻止Java垃圾回收器回收JNI代碼引用的對象;
* 另一種是弱全局引用,這種全局引用不會阻止垃圾回收器回收JNI 代碼引用的Java對象
* - NewGlobalRef用來創建強全局引用的JNI對象
* - DeleteGlobalRef用來刪除強全局引用的JNI對象
* - NewWeakGlobalRef用來創建弱全局引用的JNI對象
* - DeleteWeakGlobalRef用來刪除弱全局引用的JNI對象
* - IsSameObject用來判斷兩個JNI對象是否相同
*/
jobject gThiz; //全局JNI對象引用
jobject gWeakThiz;//全局JNI對象弱應用
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testJNIReference(JNIEnv *env, jobject instance,jobject obj) {
//聲明局部變量clazz
jclass clazz = env->GetObjectClass(instance);
//手動釋放 局部變量 clazz ;DeleteLocalRef 也可不用手動調用,JNI方法返回之后,會自動釋放局部JNI變量
env->DeleteLocalRef(clazz);
//---- 強全局變量
gThiz = env->NewGlobalRef(obj);//生成全局的JNI 對象引用,這樣生成的全局的JNI對象 才可以在其他函數中使用
env->DeleteGlobalRef(gThiz);//在我們不需要gThis這個全局JNI對象應用時,可以將其刪除。
//---- 全局弱引用
gWeakThiz = env->NewWeakGlobalRef(obj);//生成全局的JNI對象引用,這樣生成的全局的JNI對象才可以在其它函數中使用
if(env->IsSameObject(gWeakThiz,NULL)){
LOGD("全局弱引用 已經被釋放了");
}
//釋放 全局弱應用對象
env->DeleteWeakGlobalRef(gWeakThiz);
}
5、JNI 進程間同步
JNI可以使用Java對象進行線程同步
MonitorEnter函數用來鎖定Java對象
MonitorExit函數用來釋放Java對象鎖
Java類 TestNative.java
/**
* JNI 利用 java 對象進行線程同步
* @param lock
*/
public native void testJNILock(Object lock);
C++ 類 native-lib.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testJNILock(JNIEnv *env, jobject instance,
jobject lock) {
//加鎖
env->MonitorEnter(lock);
//doSomething
LOGD("feifei, this is in lock");
//釋放鎖
env->MonitorExit(lock);
}
6、JNI異常相關的函數
JNI處理Java異常
當JNI函數調用的Java方法出現異常的時候,并不會影響JNI方法的執行,但是我們并不推薦JNI函數忽略Java方法出現的異常繼續執行,這樣可能會帶來更多的問題。我們推薦的方法是,當JNI函數調用的Java方法出現異常的時候,JNI函數應該合理的停止執行代碼。
ExceptionOccurred函數用來判斷JNI函數調用的Java方法是否出現異常
ExceptionClear函數用來清除JNI函數調用的Java方法出現的異常
/**
* 1、env->ExceptionOccurred() 判斷JNI調用java方法 是否遇到了Exception
* 2、env->ThrowNew() JNI 可以主動拋出Java Exception異常
*/
public native void testJavaException();
C++ 類 native-lib.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testJavaException(JNIEnv *env, jobject instance) {
jclass clazz = env->GetObjectClass(instance);
if(clazz == NULL) return;
jmethodID helloException_method = env->GetMethodID(clazz,"helloException","()V");
if(helloException_method == NULL )return;
env->CallVoidMethod(instance,helloException_method);
if(env->ExceptionOccurred() != NULL){
// env->ExceptionDescribe();
env->ExceptionClear();
LOGD("feifei,調用java 方法時 遇到了Exception");
return;
}
LOGD("feifei,調用helloException 方法成功了!");
LOGD("feifei,now JNI throw java exception - beging");
jclass expetionClazz = env->FindClass("java/lang/Exception");
if(expetionClazz == NULL) return;
env->ThrowNew(expetionClazz,"this is a exception");
}
JNI拋出Java類型的異常
JNI通過ThrowNew函數拋出Java類型的異常
Java類 TestNative.java
LOGD("feifei,now JNI throw java exception - beging");
jclass expetionClazz = env->FindClass("java/lang/Exception");
if(expetionClazz == NULL) return;
env->ThrowNew(expetionClazz,"this is a exception");
四 JNI 和 Java對象的互相持有
Java對象持久化C/C++對象實例
通常的做法是 將C++對象指針 強轉為jlong 類型,保存在調用者java對象的long型變量中,一直持有。
當需要使用該C++對象時,從Java對象中的long變量,強轉化為C++對象,進而使用。
TestNative.java
public class TestNatvie {
static {
System.loadLibrary("native-lib");
}
/**
* 用戶保存 C++對象的引用
*/
private long mNatvieId;
/**
* Java 對象持有 C++對象
*/
public native void initSDK();
/**
* Java 對象釋放 C++對象
*/
public native void releasSDK();
}
native-lib.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_initSDK(JNIEnv *env, jobject instance) {
Person * person = new Person();
person->setAge(18);
person->initSDK();
jclass classzz = env->GetObjectClass(instance);
jfieldID fid = env->GetFieldID(classzz,"mNatvieId","J");
//將C++對象的地址綁定到Java變量中
env->SetLongField(instance,fid,(jlong)person);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_releasSDK(JNIEnv *env, jobject instance) {
jclass objectClass = env->GetObjectClass(instance);
jfieldID fid = env->GetFieldID(objectClass,"mNatvieId","J");
//取出java對象中保存的C++對象地址
jlong p = env->GetLongField(instance,fid);
//轉換成 C++對象
Person * person = (Person*)p;
person->releaseSDK();
//釋放person C++對象
free(person);
env->SetLongField(instance,fid,-1);
}
C/C++ 持久化Java對象
一般做法是
在本地方式中,創建一個全局引用 保存java對象:
env->NewGlobalRef(obj);
這樣在其他的JNI方法中就可以任意的使用該java對象了。
在不需要改java對象時,再將JNI全局引用刪除即可。
env->DeleteGlobalRef(gThiz);
使用示例:
TestNative.cpp
/**
* 利用JNI全局引用持有java 對象
*/
public native void testJNIReference(Object object);
natvie-lib.cpp
jobject gThiz; //全局JNI對象引用 - 用于持有特定的java對象。
jobject gWeakThiz;//全局JNI對象弱應用
extern "C"
JNIEXPORT void JNICALL
Java_com_example_feifei_testjni_TestNatvie_testJNIReference(JNIEnv *env, jobject instance,jobject obj) {
//---- 強全局變量
gThiz = env->NewGlobalRef(obj);//生成全局的JNI 對象引用,這樣生成的全局的JNI對象 才可以在其他函數中使用
env->DeleteGlobalRef(gThiz);//在我們不需要gThis這個全局JNI對象應用時,可以將其刪除。
}
參考文章
總結
以上是生活随笔為你收集整理的jni java_JNI 常见用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 攻防世界php2_攻防世界-web2
- 下一篇: java 权重_java实现权重随机算法