android JNI数据结构传递实例
之前做一个智能家居项目的时候,在C代码端有个数据结构需要app层传递数据,其结构体如下:
typedef struct
{
uint8_t type;
union
{
char name[PL_MAX_NAME + 1];
char loc[PL_MAX_LOC + 1];
pl_prod_type_t prod_type;
pl_prod_id_t prod_id;
uint8_t interval;
pl_uuid_t uuid;
pl_nw_sts_t nw_sts;
uint8_t incl_sts;
uint8_t grnt_keys;
uint8_t boot_mode;
} info;
} pl_info_t;
可以看到,这个结构体中包含了一乐int型type属性,以及一个info联合体,联合体中又有对应的product type结构体和product id结构体。
那么要怎样在java端定义相应的数据结构来完成这项工作呢?
通过抽象,我们将该C类型结构体在java端通过一个class来实现,具体的实现如下。
定义一个类ZwaveProvisionList代表整个pl_info_t结构体。
public class ZwaveProvisionList {
/** Meta data information type*/
public static final int PL_INFO_TYPE_PROD_TYPE = 0; ///< Product type
public static final int PL_INFO_TYPE_PROD_ID = 1 ; ///< Product ID
/*******************************************************************/
public int type; //pl info type
public ProvisionListInfo stProvisionList; //pl detail info
public class ProvisionListInfo {
public String name; // device name
public String loc; // device location
/**< For type PL_INFO_TYPE_INC_INTV; Smart Start inclusion request interval in unit of 128 seconds.
This field must have value ranging from 5 to 99. */
public int interval;
/*
Inclusion status of the provisioning list entry
#define PL_INCL_STS_PENDING 0 // init state
#define PL_INCL_STS_PASSIVE 2
#define PL_INCL_STS_IGNORED 3
*/
public int inclusionState;
/* Bootstrapping mode
#define PL_BOOT_MODE_S2 0
#define PL_BOOT_MODE_SMART_STRT 1
*/
public int boot_mode;
public class ProductTypeInfo{
public int generic_cls; /**< Generic device class*/
public int specific_cls; /**< Specific device class*/
public int icon_type; /**< Installer icon type*/
public ProductTypeInfo()
{
this.generic_cls = 0;
this.specific_cls = 0;
this.icon_type = 0;
}
}
public ProductTypeInfo pti; // product type info
public class ProductIdInfo{
public int manf_id; /**< Manufacturer ID*/
public int prod_type; /**< Product type*/
public int prod_id; /**< Product ID*/
public int app_ver; /**< Application version*/
public int app_sub_ver; /**< Application sub version*/
public ProductIdInfo(){
this.manf_id = 0;
this.prod_type = 0;
this.prod_id = 0;
this.app_ver = 0;
this.app_sub_ver = 0;
}
}
public ProductIdInfo pii; // product id info
public ProvisionListInfo(){
this.name = null;
this.loc = null;
this.inclusionState = PL_INCL_STS_PENDING; // default status: pending will auto start incl
this.interval = 100;
this.boot_mode = PL_BOOT_MODE_SMART_STRT; // default mode: smart start
this.pti = new ProductTypeInfo();
this.pii = new ProductIdInfo();
}
};
public ZwaveProvisionList()
{
this.type = 0;
this.stProvisionList = new ProvisionListInfo();
}
};
其内嵌了三个内部类,分别用于表示listInfo, productTypeInfo以及productIdInfo(省略其他无关变量),这样就和上面的C结构体对应上了。在添加上一些必要的数据获取方法就OK了:
public void setType(int type)
{
this.type = type;
}
public void setBootMode(int bootmode){
this.stProvisionList.boot_mode = bootmode;
}
public void setInclusionState(int state){
this.stProvisionList.inclusionState = state;
}
现在我们剩下的另外一个问题就是在JNI层怎样获取java端传下来的该结构体,然后转化赋值到对应的C结构体中呢?
{"ZwController_addProvisionListEntry", "([BI[Ljava/lang/Object;I)I", (void*)controller_addProvisionListEntry}, // 签名
static int controller_addProvisionListEntry(JNIEnv *env, jclass object, jbyteArray dsk, jint dsklen, jobjectArray plInfo, jint infoCnt)
上面便是jni的函数签名和定义,我们接着看在该函数体中怎样获取传递下来的plInfo对象。
首先我们要找到对应的jclass对象,即找到java端对应类的全限定类名,package+ClassName
通过JNI标准的API即可得到,注意,class的完整全限定类名千万别写错了,否则类加载器将无法获取到对应的类:
jclass objectClass = (env)->FindClass("com/xxx/xxx/application/ZwaveProvisionList"); // pkg+ClassName
接着在jni层申请出对应的C代码结构体对象内存:
pl_info_t * pl_info = (pl_info_t*)malloc(sizeof(pl_info_t) * infoCnt);
由于上面的C结构体为联合体类,因此,我们的infoCnt则决定了这个联合体的类型个数。
provision_list = env->GetObjectArrayElement(plInfo, i)可以获取到我们的第i的结构体对象,通过for循环,依次遍历取出。
for(int i = 0; i < infoCnt; i++)
{
provision_list = env->GetObjectArrayElement(plInfo, i);
if(provision_list == NULL)
{
ALOGE("GetIntArrayElements Failed");
return -1;
}
jfieldID bType = (env)->GetFieldID(objectClass, "type", "I");
pl_info[i].type = (env)->GetIntField(provision_list, bType);
jclass stProvisionListClass = env->FindClass("com/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo");
jfieldID plInfo_st = env->GetFieldID(objectClass, "stProvisionList", "Lcom/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo;");
jfieldID name = env->GetFieldID(stProvisionListClass, "name", "Ljava/lang/String;");
jfieldID location = env->GetFieldID(stProvisionListClass,"loc", "Ljava/lang/String;");
jfieldID interval = env->GetFieldID(stProvisionListClass,"interval", "I");
jfieldID inclusion_state = env->GetFieldID(stProvisionListClass,"inclusionState", "I");
jfieldID bootmode = env->GetFieldID(stProvisionListClass,"boot_mode", "I");
jobject plInfoClassObject = env->GetObjectField(provision_list, plInfo_st); //
jstring jstr = (jstring)env->GetObjectField(plInfoClassObject, name);
const char* pName = NULL;
if(jstr)
{
pName = (char*)env->GetStringUTFChars(jstr, 0);
}
jstring jstr1 = (jstring)env->GetObjectField(plInfoClassObject, location);
const char* pLoc = NULL;
if(jstr1)
{
pLoc = (char*)env->GetStringUTFChars(jstr1, 0);
}
jclass ptiClass = env->FindClass("com/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo$ProductTypeInfo");
jfieldID pti_st = env->GetFieldID(stProvisionListClass, "pti", "Lcom/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo$ProductTypeInfo;");
jfieldID generic_cls = env->GetFieldID(ptiClass,"generic_cls", "I");
jfieldID specific_cls = env->GetFieldID(ptiClass,"specific_cls", "I");
jfieldID icon_type = env->GetFieldID(ptiClass,"icon_type", "I");
jobject plPtiObject = env->GetObjectField(plInfoClassObject, pti_st); //
jclass piiClass = env->FindClass("com/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo$ProductIdInfo");
jfieldID pii_st = env->GetFieldID(stProvisionListClass, "pii", "Lcom/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo$ProductIdInfo;");
jfieldID manf_id = env->GetFieldID(piiClass,"manf_id", "I");
jfieldID prod_type = env->GetFieldID(piiClass,"prod_type", "I");
jfieldID prod_id = env->GetFieldID(piiClass,"prod_id", "I");
jfieldID app_ver = env->GetFieldID(piiClass,"app_ver", "I");
jfieldID app_sub_ver = env->GetFieldID(piiClass,"app_sub_ver", "I");
jobject plPiiObject = env->GetObjectField(plInfoClassObject, pii_st); //
z这里用到的一个常用JNI API为
jfieldID bType = (env)->GetFieldID(objectClass, "type", "I");
上面的一系列调用,遵循如下规则:
根据类名和fieldid查找对应java类的属性值type, 参数(objectclass为属性所在的类的jclass对象,由上面的FindClass获取到的, 第二个参数对应的就是java端对应的属性名,定义的是啥就是啥,千万不能写错。第三个参数为这个属性值的类型,需要对应到jni端的签名类型, 如果其类型为一个对象,则需要使用L/xx/xx/xx;切记带L的都需要加分号,否则会错)。
其他JNI包含的方法如下:
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID);
jchar (*GetCharField)(JNIEnv*, jobject, jfieldID);
jshort (*GetShortField)(JNIEnv*, jobject, jfieldID);
jint (*GetIntField)(JNIEnv*, jobject, jfieldID);
jlong (*GetLongField)(JNIEnv*, jobject, jfieldID);
jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID);
jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID);
根据域值取到对应的值,这里的参数一定要注意, 由于取得值在我们传下来的 jobjectArray plInfo中,而这个数组我们一次取每一个对象,将其存储在了provision_list 中,所以我们取根类的属性type的值时,通过的下面的方法中传入的object是provision_list,这里千万要注意。
jobject provision_list = env->GetObjectArrayElement(plInfo, i);
于是得到其type属性并填充:
pl_info[i].type = (env)->GetIntField(provision_list, bType);
下面获取内部类的属性值,首先取到内部类的class对象,内部类的全限定名称为根类+"$"+内部类名, 然后找到对应的内部类对象的属性值,同上,需要注意类型字符串需要加上分号,分号,分号!!!
jclass stProvisionListClass = env->FindClass("com/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo");
jfieldID plInfo_st = env->GetFieldID(objectClass, "stProvisionList", "Lcom/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo;");
接下来同理,取到各个属性的域值,很简单,注意类型即可,对象类型需要加分号,字注意:符串类型也属于对象
jfieldID name = env->GetFieldID(stProvisionListClass, "name", "Ljava/lang/String;");
jfieldID location = env->GetFieldID(stProvisionListClass,"loc", "Ljava/lang/String;");
jfieldID interval = env->GetFieldID(stProvisionListClass,"interval", "I");
jfieldID inclusion_state = env->GetFieldID(stProvisionListClass,"inclusionState", "I");
jfieldID bootmode = env->GetFieldID(stProvisionListClass,"boot_mode", "I");
接下来又到了一个关键地方:
拿到传递下来的内部类对象的属性值,应该先取到对应内部类的对象。由于对应的值在provision_list里面,所以这里的对象也在这里面取:
// 由于jfieldID plInfo_st = env->GetFieldID(objectClass, "stProvisionList",
// "Lcom/xxx/xxx/application/ZwaveProvisionList$ProvisionListInfo;");
// 然后就可以在这个Object下取域值了。
jobject plInfoClassObject = env->GetObjectField(provision_list, plInfo_st);
字符串类型获取如下,对应C的char[20],然后使用stycpy拷贝
jstring jstr1 = (jstring)env->GetObjectField(plInfoClassObject, location);
const char* pLoc = NULL;
if(jstr1)
{
pLoc = (char*)env->GetStringUTFChars(jstr1, 0);
}
通过上面的方式便可以完整的取到我们的java类型数据结构了,后面抽空再讲讲C/C++中怎样通过JNI传递C类型到java端。