一、前言
最近在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