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端。
posted @ 2018-07-09 16:51  mail181  阅读(115)  评论(0)    收藏  举报