Android studio中NDK開發(五)——C嵌套結構體與Java類在JNI層的傳輸

一、前言

最近在Android上的NDK開發時遇到一個問題,在Java層需要獲取到設備的註冊信息,然後在JNI層將這些信息封裝爲結構體參數的形式傳遞到C++中的方法中進行處理。也就是說,在Java層獲取到的信息需要先轉換成結構體,再傳進去,在C++和Java的JNI層轉換的這個過程中整整卡了兩三天,一直找不到解決問題的思路。


二、分析

從結構體的特性來看,其實結構體就是不同屬性的合集,只不過嵌套結構體是在結構體內部還包含了一個結構體。而在Java層中,類的特性和結構體比較類似,而且用Java的類來替換比較直觀,類中的屬性和結構體中的屬性可以一一對應。舉一反三,嵌套結構體對應的自然就是嵌套類了(嵌套類指的是含有內部類的外部類)。


三、舉例

嵌套結構體如下:

typedef struct _Stru_Outer_Info                // 外部結構體
{
    StruInnerInfo struInnerInfo;            // 嵌套的內部結構體
    char outerName[OUTERNAME_LEN];            // 外部結構體名稱

}StruOuterInfo;

typedef struct _Stru_Inner_Info                // 內部結構體
{
    char innerName[INNERNAME_LEN];            // 內部結構體名稱
     
}StruInnerInfo;

需要傳進該嵌套結構體的方法:

void callInnerClassField(const StruOuterInfo* struOuterInfo);    // 需傳進嵌套結構體
1

1、Java層聲明一個嵌套類來對應C++中的嵌套結構體
(注:這裏嵌套類用Outer和Inner類來對應比較直觀,StruOuterInfo對應Outer類,StruInnerInfo對應內部類)

// 外部類OuterClass 對應  StruOuterInfo外部結構體
public class OuterClass {                    

    private String outerName;
    private InnerClass innerClass;

    OuterClass(){                            // 外部類構造函數,對內部類實例化
        innerClass = new InnerClass();
    }

    public InnerClass getInnerClass(){        // 獲取內部類對象,在Java層調用該方法即可獲得內部類對象InnerClass,並使用該對象對屬性進行設置                                       
        return innerClass;
    }

    public void setOuterName(String name){
        this.outerName = name;
    }

    public String getOuterName() {
        return outerName;
    }
// 內部類InnerClass 對應 StruInnerInfo內部類結構體
    class InnerClass{

        private String innerName;

        public String getInnerName() {
            return innerName;
        }
    
        public void setInnerName(String name){
            this.innerName = name;
        }
    }
}

注意:
在外部類的構造函數中進行內部類的實例化,並聲明瞭一個提供內部類對象的方法。在Java調用的時候一定要通過getInnerClass()來獲取內部類的對象,不能通過new來創建。如果通過new來創建,那麼是分配一個新的內存空間,在C++層中獲取到的不再是同一個對象!


2、在JNI層創建一個與C++中的callInnerClassField相對應的方法

extern "C" JNIEXPORT void JNICALL
Java_com_example_hasee_ndkdemo_NDKUtil_callInnerClassField(                    // 注意方法名格式:Java_包名_類名_方法名
        JNIEnv *env, jobject instance,jobject outerClassObject) {            // 這裏需要傳入一個外部類對象參數,與外部結構體相對應
        
    // 外部類
    jclass outer_class_cls = env -> GetObjectClass(outerClassObject);                                    // 通過外部類對象獲取外部類的引用
    jfieldID outer_name_fid = env -> GetFieldID(outer_class_cls,"outerName","Ljava/lang/String;");        // 通過外部類引用獲取外部類的屬性ID
    jstring outerName = (jstring)env -> GetObjectField(outerClassObject,outer_name_fid);                // 通過外部類對象和屬性ID獲取外部類的屬性 

    if (outerName == NULL){
        LOGE("外部類名字爲空!!!" );
    }else{
        const char *outerName_ = env -> GetStringUTFChars(outerName, 0);
        LOGE("外部類名字:%s" , outerName_);
        env -> ReleaseStringUTFChars(outerName,outerName_);                // 記得手動釋放字符串所佔用的內存空間
    }
    
    // 關鍵部分  將InnerClass作爲外部類的一個屬性,通過外部類獲取屬性ID的方法來獲取到內部類的對象引用
    jfieldID inner_class_fid = env -> GetFieldID(outer_class_cls,"innerClass","Lcom/example/hasee/ndkdemo/OuterClass$InnerClass;");            
    jobject innerClassObject = env -> GetObjectField(outerClassObject,inner_class_fid);                    // 獲取到內部類對象引用
    jclass inner_class_cls = env -> GetObjectClass(innerClassObject);                                    // 通過內部類對象引用獲取內部類引用
    jfieldID inner_name_fid = env -> GetFieldID(inner_class_cls,"innerName","Ljava/lang/String;");        // 同過內部類引用獲取到內部類屬性ID,注意屬性簽名爲Ljava/lang/String後面記得還要加上“;”
    jstring innerName = (jstring)env -> GetObjectField(innerClassObject,inner_name_fid);                // 通過內部類對象和屬性ID獲取內部類的屬性 
    
    if (innerName == NULL){
        LOGE("內部類名字爲空!!!!!!");
    }else{
        const char *innerName_ = env -> GetStringUTFChars(innerName,0);
        LOGE("內部類名字:%s",innerName_);
        env -> ReleaseStringUTFChars(innerName,innerName_);                // 記得手動釋放字符串所佔用的內存空間
    }

注意:
1)屬性簽名:引用類型的簽名要記得在後面加上“;”,否則識別不到該屬性;
2)記得對獲取到的屬性ID、方法ID等進行判空,有特殊情況沒有獲取到的話應用就崩了。因爲我這裏爲了方便閱讀,暫時沒有加上。
3)這裏只是將獲取到內部類屬性,即 innerName ,進行JNI層的打印,如果能打印出來,證明可以訪問到內部類的屬性。因爲該例子只是爲了驗證能不能訪問到內部類的屬性,所以暫不做其他處理。


3、將JNI層的方法封裝到Java層的native方法

    public native void callInnerClassField(Object outerClassObject);
1

4、在Activity中調用

    
// 測試 Java層嵌套類與C/C++層的嵌套結構體的對應傳參
    OuterClass outerClass = new OuterClass();
    OuterClass.InnerClass innerClass = outerClass.getInnerClass();    // 注意:這裏是通過getInnerClass()方法來獲取內部類對象,而不是通過new來創建
    outerClass.setOuterName("I am OuterClass");                        // Java層對嵌套類的外部類進行屬性設置
    innerClass.setInnerName("I am InnerClass");                        // Java層對嵌套類的內部類進行屬性設置
    
    NdkUtil ndkUtil = new NdkUtil();                                // 我自己創建用來加載第三方so庫以及存放native方法的工具類
    ndkUtil.callInnerClassMethod(outerClass);                        // 調用native方法

注意:
這裏一定要通過getInnerClass()方法來獲取內部類對象,如果使用new來創建的話,C++層的內部類對象和該對象指向的是不同的內存地址空間,new出來的內部類對象賦值了,但C++層指向的是另外的內部類對象,還沒賦值,獲取到的內部類屬性自然爲空,這也是我這兩天打印屬性(innerName)一直爲空的原因!!!


5、效果驗證

========= Error =========: 外部類名字:I am OuterClass
========= Error =========: 內部類名字:I am InnerClass
 

如有錯誤,歡迎指正,虛心學習!
————————————————
版權聲明:本文爲CSDN博主「Xiongjiayo」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Xiongjiayo/article/details/86484115

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