JNI視頻教程 筆記(二)

第5課  
1. 在本地代碼中,創建String對象
 jstring NewString(const jchar* str,jsize len);   //傳入一個寬字符串及長度,就能創建一個java的string對象
  jstring NewStringUTF(const char* str);      //傳入一個UTF-8格式的字符串就可以
 爲什麼不用傳入字符串長度呢?C/C++中字符串都是以'/0'結尾的,通過這個‘/0’就能知道字符串的長度了。
jsize GetStringLength(jstring str);
jsize GetStringUTFLength(jstring str); //字符串以UTF-8格式存儲時佔用多少個字節。
代碼示例:
MainTest.java
package cn.itcast;
public class MainTest{
    public String message=null;
    public native void callCppFun();
    public static void main(String[] args) {
        System.loadLibrary("JavaString");
        /*用戶輸入一些字符串,在本地代碼中訪問這個字符串*/    
        BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
        String str=reader.readLine();
        MainTest obj=new MainTest();
        obj.callCppFun();
        System.out.println("Java output:"+obj.message);
    }
}
source.cpp
#include "cn_itcast_TestNative.h"
#include <iostream>
using namespace std;
 
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{    
    /*得到MainTest類的message屬性ID,這個message的類型是String*/
    jfieldID fid_msg=env->GetFieldID(env->GetObjectClass(obj),"message","Ljava/lang/String");
    jstring j_msg=(jstring)env->GetObjectField(obj,fid_msg);/*這個j_msg就代表了java中的message*/
 
    const jchar* jstr=env->GetStringChars(j_msg,NULL); /*將java中的字符串轉換到本地的字符串*/
    MessageBox(NULL,(const wchar_t*)jstr,L"Title",MB_OK);  /*用對話框顯示出來*/
    wstring wstr((const wchar_t*)jstr);   /*將jstr拷貝一下,成寬字符串*/
    env->ReleaseStringChars(j_msg,jstr);   /*釋放掉空間,到這裏就不需要本地字符串了*/
 
    std::reverse(wstr.begin(),wstr.end()); //能夠讓容器中的內容倒序。
    jstring j_new_str=env->NewString((const jchar*)wstr.c_str(),(jint)wstr.size()); /*通過wstr得到一個新的java的字符串*/
    env->SetObjectFile(obj,fid_msg,j_new_str);  /*將字符串倒序後,設置回去*/
}
方法二:在本地創建一個字體數組
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{    
    /*得到MainTest類的message屬性,這個message的類型是String*/
    jfieldID fid_msg=env->GetFieldID(env->GetObjectClass(obj),"message","Ljava/lang/String");
    jstring j_msg=(jstring)env->GetObjectField(obj,fid_msg);
 
    jsize len=env->GetStringLength(j_msg);
    jchar* jstr=new jchar[len+1];
    jstr[len]=L'\0';
    env->GetStringRegion(j_msg,0,len,jstr);/*將java的字符串拷貝到本地的字符數組中了*/
 
    wstring wstr((const wchar_t*)jstr);
    delete [] jstr;
 
    std::reverse(wstr.begin(),wstr.end()); //能夠讓容器中的內容倒序。
    jstring j_new_str=env->NewString((const jchar*)wstr.c_str(),(jint)wstr.size());
    env->SetObjectFile(obj,fid_msg,j_new_str);  /*將字符串倒序後,設置回去*/
}

第6課 處理數組  
數組分爲兩種:
 1.基本類型的數組
 2.對象類型(Object [])的數組
1. 一個能通用於兩種不同類型數組的函數:

 GetArrayLength(jarray array);

處理數組--基本類型數組
1. Get<TYPE>ArrayElements(<TYPE>Array arr, jboolean* isCopied);
  這個函數可以把java基本數組轉換到C/C++中的數組,有兩種處理方式,一是拷貝一份傳回本地代碼,另一個是把指向java數組的指針直接傳回到本地代碼。
 處理完成本地化的數組後,通過 Release<TYPE>ArrayElements來釋放數組。
  Release<TYPE>ArrayElements(<TYPE>Array arr,<TYPE>* array,jint mode);

用這個函數可以先把將如何處理java跟C++的數組,是提交還是撤銷等,內存釋放還是不釋放等。

mode 可以取下面的值:
0                             ===》 對java的數組進行更新並釋放C/C++中的數組
JNI_COMMIT            ===》對java的數組進行更新但不釋放C/C++中的數組
JNI_ABIRT                ===》 對java的數組不進行更新,釋放C/C++中的數組
 GetPrimitiveArrayCritical(jarray arr,jboolean* isCopied);
ReleasePrimitiveArrayCritical(jarray arr,void* array,jint mode);
爲了增加直接會加指向java數組的指針而加入的函數,同樣的,也會有死鎖的問題。

2.Get<TYPE>ArrayRegion(<TYPE>Array arr,jsize start,jsize len,<TYPE>* buffer);
 在C/C++預先開闢一段內存,然後把java基本類型的數組拷貝到這段內存中,跟GetStringRegion原理類似。
  Set<TYPE>ArrayRegion(<TYPE>Array arr,jsize start,jsize len,<TYPE>* buffer);
 把java基本類型的數組中的指定範圍的元素內 C/C++ 的數組中的元素來賦值。
3.<TYPE>Array New<TYPE>Array(jsize sz);
指定一個 長度然後返回相應java基本類型的數組。
處理數組--對象類型數組 (Object [ ])
1. JNI沒有提供直接把Java的對象類型數組(Object[])直接轉到C++中的jobject[]數組的函數。而是直接 通過Get/SetObjectArrayElement這樣的函數來對Java的Object[]數組進行操作。
jobject GetObjectArrayElement(jobjectArray array,jsize index);
void SetObjectArrayElement(jobjectArray array,jsize index,jobject val);
使用以上函數不用釋放 任何資源。
2. NewObjectArray 可以通過指定長度跟初始值來創建某個類的數組。
代碼示例:在本地代碼中訪問java中的基本數組
TestNative.java
package cn.itcast;
public class MainTest{
    public int[] arrays={1,2,3,4,5,6,9,2};
    public native void callCppFun();
    public static void main(String[] args) {
        System.loadLibrary("JavaString");        
        MainTest obj=new MainTest();
        obj.callCppFun();
        for(int each:obj.arrays)
        {
            System.out.println(each);
        }        
    }
}
source.cpp
#include "cn_itcast_TestNative.h"
#include <iostream>
#include <algorithm>
using namespace std;
 
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{    
    jfieldID fid_arrays=env->GetFieldID(env->GetObjectClass(obj),"arrays","[I");
    jintArray jint_arr=(jintArray)env->GetObjectField(obj,fid_arrays);
 
    jint* int_arr=env->GetIntArrayElements(jint_arr,NULL);
    jsize len=env->GetArrayLength(jint_arr);
    for(jsize i=0;i<len;++i)
    {
        cout<<int_arr[i]<<endl;
    }
    std::sort(int_arr,int_arr+len);  //排序
    env->ReleaseIntArrayElements(jint_arr,int_arr,JNI_ABORT);
}
在本地代碼中成功訪問了java中的數組。
第7課 全局引用/局部引用/弱全局引用

1. 從Java虛擬機創建的對象傳到本地C/C++代碼時會產生引用。根據java的垃圾回收機制,只要有引用存在就不會觸發該引用指向的Java對象的垃圾回收。

  這些引用在JNI中分爲三種:
 全局引用(Global Reference)
 局部引用(Local Reference)
 弱全局引用(Weak Global Reference) 
2.局部引用
  最常見的引用類型,基本上通過JNI返回來的引用都是局部引用。
  例如使用 NewObject 就會返回創建出來的實例的局部引用。局部引用只在該 native 函數中有效,所有在該函數中產生的局部引用,都會在函數返回的時候自動釋放(freed)。也可以用DeleteLocalRef函數手動釋放該引用。

想一想既然局部引用能夠在函數返回時自動釋放,爲什麼還需要DeleteLocalRef函數呢?
實際上局部引用存在,就會防止其指向的對象被垃圾回收,尤其是當一個局部引用指向一個很龐大的對象 或是在一個循環中生成了局部引用,最好的做法就是在使用完該對象後,或在該循環尾部把這個引用釋放掉,以確保在垃圾回收器被觸發的時候被回收。
在局部引用的有效期中,可以傳遞到別的本地函數中,要強調的是他的有效期仍然只在一次的Java本地函數中,所以千萬不能用C++全局變量保存他或是把他定義爲C++靜態局部變量。
3.全局引用
全局引用可以跨越當前線程,在多個native函數中有效,不過需要工程師手動來釋放該引用。全局引用存在期間會防止在Java的垃圾回收的回收。
與局部引用不同,全局引用的創建不是由JNI自動創建的,全局引用是需要調用NewGlobalRef函數,而釋放他需要使用ReleaseGlobalRef函數。
4.弱全局引用
與全局引用相似,創建跟刪除都需要由工程師來進行,這種引用與全局引用一樣可以在多個本地代碼有效,也跨越多個線程有效。不同的是,這種引用將不會阻止垃圾回收器回收這個引用所指向的對象。
使用NewWeakGloabalRef跟RelwaseWeakGlobalRef來產生和解除引用。
5.關於使用的一些函數:
jobject NewGlobalRef(jobject obj);
jobject NewGlobalRef(jobject obj);
void DeleteGlobalRef(jobject obj);
void DeleteLocalRef(jobject obj);
void DeleteWeakGlobalRef(jobject obj);
jboolean IsSameObject(jobject obj1, jobject obj2) ; //判斷obj1,obj2是否指向同一個對象。
這個函數對於弱全局引用還有一個特殊的功能,把NULL傳入要比較的對象中,就能夠判斷弱全局引用所指向的Java對象是否被回收。  
6.緩存jfieldID/jmethodID
取得 jfieldID跟jmethodID的時候會通過該屬性/方法名稱加上簽名來查詢相應的jfieldID/jmethodID.
這種查詢相對來說開銷較大,我們可以將這些jfieldID/jmethodID緩存起來,這樣只需要查詢一次,以後就使用緩存起來的jfieldID/jmethodID,
下面介紹兩種緩存方式:
 1.在用的時候緩存   
 2.在java類初始化時緩存
1.在第一次使用的時候緩存
在native code中使用static局部變量來保存已經查詢過的id.這樣就不會在每次的函數調用時查詢.
不過這種情況下就不得不考慮多線程同時呼叫此函數時可能會招致同時查詢的危機.
JNIEXPORT void JNICALL ava_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{
    static jfieldID fid_string=NULL;
    jclass clazz=env->GetObjectClass(obj);
    if(fid_string==NULL)
    {
        fid_string=env->GetFieldID(clazz,"string","Ljava/lang/String");
 
 
    }
    ........
}
2.在java類初始化的時候緩存
更好的一個方式就是在任何native函數調用前把id全部存在來.
我們可以讓java在第一次加載這個類的時候首先調用本地代碼初始化所有的jfieldID/jmethodID.這樣的話就可以省去多次的確定id是否存在的語句,當然,這些jfieldID/jmethodID是定義在C/C++的全局中。
 使用這種方式 還有好處,當java類卸載或是重新加載的時候也會重新呼叫 該本地代碼來重新計算IDs.
TestNative.java
package cn.itcast;
public class TestNative
{
    static{
        initNativeIDs();
    }
    public native void initNativeIDs();
    int propInt=0;
    String propStr="";
    public native void otherNative();
    .............    
}
source.cpp
jfieldID g_propInt_id=0;
jfieldID g_propStr_id=0;
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{    
    g_propInt_id=GetFieldID(obj,"propInt","I");
    g_propStr_id=GetFieldID(obj,"propStr","Ljava/lang/String");
 
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章