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

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