1. 程式人生 > >JNI學習積累之三 ---- 操作JNI函式以及複雜物件傳遞

JNI學習積累之三 ---- 操作JNI函式以及複雜物件傳遞

         在掌握了JNI函式的使用和相關型別的對映後,以及知曉何利用javah工具生成對應的jni函式以及如何生成動態

連結庫 (windos下就是.dll庫,Linux就是.so庫了,不懂在Window下生成dll動態庫的,具體流程可看我的這篇部落格:

        總的來說,JNI是不難的。通過前面的學習相信你應該有所瞭解。今天,我們從幾個簡單的小例子,來對JNI進行下實戰訓練。

     可都是些小例子,耐心看咯。

        主要操作內容,包括如下幾個部分:

     1、在Native層返回一個字串

    2、從Native層返回一個int型二維陣列(int a[ ][ ]) 

  3、從Native層操作Java層的類: 讀取/設定類屬性

               4、在Native層操作Java層的類:讀取/設定類屬性、回撥Java方法 

               5、從Native層返回一個複雜物件(即一個類咯)

               6、在Java層傳遞複雜物件至Native層

               7、從Native層返回Arraylist集合物件

      廣而告知,這些操作就是簡單的利用一些JNI函式即實現了。so easy 。

 一、在Native層返回一個字串

       Java層原型方法:

public class HelloJni {
    ...
	public native void getAJNIString();
    ...
}	

    Native層該方法實現為 :

/*
 * Class:     com_feixun_jni_HelloJni
 * Method:    getAJNIString
 * Signature: ()Ljava/lang/String;
 */ 
//返回字串
JNIEXPORT jstring JNICALL Java_com_feixun_jni_HelloJni_getAJNIString(JNIEnv * env, jobject obj)
{
    jstring str = env->newStringUTF("HelloJNI");  //直接使用該JNI構造一個jstring物件返回
	return str ;
}

二、在Native層返回一個int型二維陣列(inta[ ][ ])

    Java層原型方法:

public class HelloJni {
	...
	//引數代表幾行幾列陣列 ,形式如:int a[dimon][dimon]
	private native int[][] getTwoArray(int dimon) ; 
	...
}	

      Native層該方法實現為 :
/*
 * Class:     com_feixun_jni_HelloJni
 * Method:    getTwoArray
 * Signature: (I)[[I
 */
//通過構造一個數組的陣列, 返回 一個二維陣列的形式
JNIEXPORT jobjectArray JNICALL Java_com_feixun_jni_HelloJni_getTwoArray
  (JNIEnv * env, jobject object, jint dimion)
{
	
	jclass intArrayClass = env->FindClass("[I"); //獲得一維陣列 的類引用,即jintArray型別
	//構造一個指向jintArray類一維陣列的物件陣列,該物件陣列初始大小為dimion
	jobjectArray obejctIntArray  =  env->NewObjectArray(dimion ,intArrayClass , NULL);

    //構建dimion個一維陣列,並且將其引用賦值給obejctIntArray物件陣列
	for( int i = 0 ; i< dimion  ; i++ )
	{
		//構建jint型一維陣列
		jintArray intArray = env->NewIntArray(dimion);

        jint temp[10]  ;  //初始化一個容器,假設 dimion  < 10 ;
		for( int j = 0 ; j < dimion ; j++)
		{
            temp[j] = i + j  ; //賦值
		}
		
		//設定jit型一維陣列的值
        env->SetIntArrayRegion(intArray, 0 , dimion ,temp);
        //給object物件陣列賦值,即保持對jint一維陣列的引用
		env->SetObjectArrayElement(obejctIntArray , i ,intArray);

		env->DeleteLocalRef(intArray);  //刪除區域性引用
	}

    return   obejctIntArray; //返回該物件陣列
}


 三、在Native層操作Java層的類 :讀取/設定類屬性

     Java層原型方法:

public class HelloJni {
	...
	//在Native層讀取/設定屬性值
	public native void native_set_name() ;
	...
	
	private String name = "I am at Java" ; //類屬性
}	

    Native層該方法實現為 :
/*
 * Class:     com_feixun_jni_HelloJni
 * Method:    native_set_name
 * Signature: ()V 
 */
//在Native層操作Java物件,讀取/設定屬性等
JNIEXPORT void JNICALL Java_com_feixun_jni_HelloJni_native_1set_1name
  (JNIEnv *env , jobject  obj )  //obj代表執行此JNI操作的類例項引用
{
   //獲得jfieldID 以及 該欄位的初始值
   jfieldID  nameFieldId ;

   jclass cls = env->GetObjectClass(obj);  //獲得Java層該物件例項的類引用,即HelloJNI類引用

   nameFieldId = env->GetFieldID(cls , "name" , "Ljava/lang/String;"); //獲得屬性控制代碼

   if(nameFieldId == NULL)
   {
	   cout << " 沒有得到name 的控制代碼Id \n;" ;
   }
   jstring javaNameStr = (jstring)env->GetObjectField(obj ,nameFieldId);  // 獲得該屬性的值
   const char * c_javaName = env->GetStringUTFChars(javaNameStr , NULL);  //轉換為 char *型別
   string str_name = c_javaName ;  
   cout << "the name from java is " << str_name << endl ; //輸出顯示
   env->ReleaseStringUTFChars(javaNameStr , c_javaName);  //釋放區域性引用

   //構造一個jString物件
   char * c_ptr_name = "I come from Native" ;
   
   jstring cName = env->NewStringUTF(c_ptr_name); //構造一個jstring物件

   env->SetObjectField(obj , nameFieldId , cName); // 設定該欄位的值
}

四、在Native層操作Java層的類:回撥Java方法 

    Java層原型方法:

public class HelloJni {
	...
	//Native層回撥的方法實現
	public void callback(String fromNative){	 
	    System.out.println(" I was invoked by native method  ############# " + fromNative);
	};
	public native void doCallBack(); //Native層會呼叫callback()方法
	...	
	
	// main函式
	public static void main(String[] args) 
	{
		new HelloJni().ddoCallBack();
	}	
}	

    Native層該方法實現為 :

/*
 * Class:     com_feixun_jni_HelloJni
 * Method:    doCallBack
 * Signature: ()V
 */
//Native層回撥Java類方法
JNIEXPORT void JNICALL Java_com_feixun_jni_HelloJni_doCallBack
  (JNIEnv * env , jobject obj)
{
     //回撥Java中的方法

	jclass cls = env->GetObjectClass(obj);//獲得Java類例項
    jmethodID callbackID = env->GetMethodID(cls , "callback" , "(Ljava/lang/String;)V") ;//或得該回調方法控制代碼

	if(callbackID == NULL)
	{
		 cout << "getMethodId is failed \n" << endl ;
	}
  
	jstring native_desc = env->NewStringUTF(" I am Native");

	env->CallVoidMethod(obj , callbackID , native_desc); //回撥該方法,並且傳遞引數值
}

    接下來,我們會操作複雜物件,也就是Java層的類,包括從Native層返回一個類以及傳遞一個類到Native層去, 這兒我們

使用的類非常簡單,如下:

     Student.java

package com.feixun.jni;

public class Student
{
    private int age ;
    private String name ;
    //建構函式,什麼都不做
    public Student(){ }
    
    public Student(int age ,String name){
        this.age = age ;
        this.name = name ;
    }
    
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    
    public String toString(){
        return "name --- >" + name + "  age --->" + age ;
    }
}

 五、在Native層返回一個複雜物件(即一個類咯)

     Java層的方法對應為:

public class HelloJni {
	...
	//在Native層返回一個Student物件
	public native Student nativeGetStudentInfo() ;
	...	
}	

     Native層該方法實現為 :
/*
 * Class:     com_feixun_jni_HelloJni
 * Method:    nativeGetStudentInfo
 * Signature: ()Lcom/feixun/jni/Student;
 */
//返回一個複雜物件
JNIEXPORT jobject JNICALL Java_com_feixun_jni_HelloJni_nativeGetStudentInfo
  (JNIEnv * env, jobject obl)
{
	//關於包描述符,這兒可以是 com/feixun/jni/Student 或者是 Lcom/feixun/jni/Student; 
	//   這兩種型別 都可以獲得class引用
	jclass stucls = env->FindClass("com/feixun/jni/Student"); //或得Student類引用

	//獲得得該型別的建構函式  函式名為 <init> 返回型別必須為 void 即 V
	jmethodID constrocMID = env->GetMethodID(stucls,"<init>","(ILjava/lang/String;)V");

	jstring str = env->NewStringUTF(" come from Native ");

    jobject stu_ojb = env->NewObject(stucls,constrocMID,11,str);  //構造一個物件,呼叫該類的建構函式,並且傳遞引數


    return stu_ojb ;
}

 六、從Java層傳遞複雜物件至Native層

    Java層的方法對應為:

public class HelloJni {
	...
	//在Native層列印Student的資訊
	public native void  printStuInfoAtNative(Student stu);
	...	
}

     Native層該方法實現為 :

/*
 * Class:     com_feixun_jni_HelloJni
 * Method:    printStuInfoAtNative
 * Signature: (Lcom/feixun/jni/Student;)V
 */
//在Native層輸出Student的資訊
JNIEXPORT void JNICALL Java_com_feixun_jni_HelloJni_printStuInfoAtNative
  (JNIEnv * env, jobject obj,  jobject obj_stu) //第二個類例項引用代表Student類,即我們傳遞下來的物件
{
	
    jclass stu_cls = env->GetObjectClass(obj_stu); //或得Student類引用

	if(stu_cls == NULL)
	{
     	cout << "GetObjectClass failed \n" ;
	}
	//下面這些函式操作,我們都見過的。O(∩_∩)O~
	jfieldID ageFieldID = env->GetFieldID(stucls,"age","I"); //獲得得Student類的屬性id 
    jfieldID nameFieldID = env->GetFieldID(stucls,"name","Ljava/lang/String;"); // 獲得屬性ID

	jint age = env->GetIntField(objstu , ageFieldID);  //獲得屬性值
	jstring name = (jstring)env->GetObjectField(objstu , nameFieldID);//獲得屬性值

    const char * c_name = env->GetStringUTFChars(name ,NULL);//轉換成 char *
 
	string str_name = c_name ; 
    env->ReleaseStringUTFChars(name,c_name); //釋放引用
    
	cout << " at Native age is :" << age << " # name is " << str_name << endl ; 
}


七、最後加個難度,即在Native層返回集合物件(留這兒,以後也好找點)

     Java層的對應方法為:

public class HelloJni {
	...
	//在Native層返回ArrayList集合 
	public native ArrayList<Student> native_getListStudents();
	...	
}	

     Native層該方法實現為 :
/*
 * Class:     com_feixun_jni_HelloJni
 * Method:    native_getListStudents
 * Signature: ()Ljava/util/ArrayList;
 */ //獲得集合型別的陣列
JNIEXPORT jobject JNICALL Java_com_feixun_jni_HelloJni_native_getListStudents
  (JNIEnv * env, jobject obj)
{
    jclass list_cls = env->FindClass("Ljava/util/ArrayList;");//獲得ArrayList類引用

	if(listcls == NULL)
    {
		cout << "listcls is null \n" ;
	}
    jmethodID list_costruct = env->GetMethodID(list_cls , "<init>","()V"); //獲得得建構函式Id

	jobject list_obj = env->NewObject(list_cls , list_costruct); //建立一個Arraylist集合物件
    //或得Arraylist類中的 add()方法ID,其方法原型為: boolean add(Object object) ;
	jmethodID list_add  = env->GetMethodID(list_cls,"add","(Ljava/lang/Object;)Z"); 
  
	jclass stu_cls = env->FindClass("Lcom/feixun/jni/Student;");//獲得Student類引用
	//獲得該型別的建構函式  函式名為 <init> 返回型別必須為 void 即 V
	jmethodID stu_costruct = env->GetMethodID(stu_cls , "<init>", "(ILjava/lang/String;)V");

    for(int i = 0 ; i < 3 ; i++)
	{
	    jstring str = env->NewStringUTF("Native");
		//通過呼叫該物件的建構函式來new 一個 Student例項
        jobject stu_obj = env->NewObject(stucls , stu_costruct , 10,str);  //構造一個物件
        
        env->CallBooleanMethod(list_obj , list_add , stu_obj); //執行Arraylist類例項的add方法,新增一個stu物件
	}

	return list_obj ;
}

         最後,如何呼叫這些JNI函式,大家都懂的,直接呼叫即可,我就不在貼程式碼了,免得羅嗦。

        OK,本次JNI的學習就算告一段落了。下一步該認真仔細學習下Android中JNI的使用了。哎,怎麼學的東西又那麼多呢? - -