Android Studio NDK 入門教程(3)--Java與C++之間的型別簽名

Wastrel_xyz發表於2016-08-19

概述

本文主要介紹Java與C++通訊時函式的簽名關係

方法簽名

我們可以在生成的標頭檔案中看到每個方法上面都有如下注釋:

/*
 * Class:     com_example_wastrel_hellojni_HelloJNI
 * Method:    getFormCString
 * Signature: ()Ljava/lang/String;
 */
 JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getFormCString
  (JNIEnv *, jclass);

其中前兩個註釋很容易理解,第三個就是方法簽名,為什麼會有方法簽名這種東西呢?

這是因為Java這邊支援函式過載,即雖然引數不一樣,但是方法名一樣,那麼在JNI層它們的方法名都會是一樣的,那JNI也會犯迷糊了,得找哪個呢?
不過也正是因為其引數型別是不一樣的,所以就出現了方法簽名,利用方法簽名和方法名來唯一確定一個JNI函式的呼叫。既然方法簽名是基於引數型別的不同而形成的,首先要知道Java各資料型別對應的簽名是什麼,也就是所謂的型別簽名。

型別簽名轉換表

jni.h裡面定義了這樣一套規則:

typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;

那麼對應在JAVA中的關係如下表:

Java 型別 型別簽名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
L全限定名;
陣列 [型別簽名

注意:上面的類後面是有一個分號的。

如何計算一個方法的型別簽名

Java函式的型別簽名是根據函式的引數型別以及返回型別所決定的,引數型別放在括號裡面返回型別緊跟在括號外。

    //native測試方法宣告
    public static native String getFormCString();
    //三個過載方法
    public static native int TypeTest(boolean a,short b,long c,char d);
    public static native Objects TypeTest(Date date,byte []m,HelloJNI k);

    public static native Long Test(Character k,Double m);
    //執行生成標頭檔案命令


    /*
    * Class:     com_example_wastrel_hellojni_HelloJNI
    * Method:    getFormCString
    * Signature: ()Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getFormCString
    (JNIEnv *, jclass);

    /*
    * Class:     com_example_wastrel_hellojni_HelloJNI
    * Method:    TypeTest
    * Signature: (ZSJC)I
    */
    JNIEXPORT jint JNICALL Java_com_example_wastrel_hellojni_HelloJNI_TypeTest__ZSJC
    (JNIEnv *, jclass, jboolean, jshort, jlong, jchar);

    /*
    * Class:     com_example_wastrel_hellojni_HelloJNI
    * Method:    TypeTest
    * Signature: (Ljava/util/Date;[BLcom/example/wastrel/hellojni/HelloJNI;)Ljava/util/Objects;
    */
    JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_TypeTest__Ljava_util_Date_2_3BLcom_example_wastrel_hellojni_HelloJNI_2
    (JNIEnv *, jclass, jobject, jbyteArray, jobject);


    /*
    * Class:     com_example_wastrel_hellojni_HelloJNI
    * Method:    Test
    * Signature: (Ljava/lang/Character;Ljava/lang/Double;)Ljava/lang/Long;
    */
    JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_Test
    (JNIEnv *, jclass, jobject, jobject);

通過以上程式碼可以可以看出,過載的方法生成的標頭檔案後函式名後面都追加引數的型別簽名,並且‘/’被替換成了下劃線‘_’,‘;’被替換成了數字‘2’;‘[’被替換成了數字‘3’。在簽名中()裡面表示方法的引數,緊接著的是返回型別對應的簽名。可以看到String getFromCString因為沒有引數,所以()裡面什麼都沒有,但其有一個返回引數String,String並不是Java的基礎型別,String的全稱是java.lang.String,根據簽名規則就是Ljava/lang/String;,因此該方法的簽名是()Ljava/lang/String。這裡需要注意,如果一個Java函式沒有返回即void,那麼對應的返回型別簽名就是V。

其餘函式有興趣可以根據上述規則推理,我相信各位程式設計師的推理能力,這裡就不一一講解。

後記

方法簽名是為了便於Java與native程式碼的相互訪問,後面通過C++呼叫Java程式碼的時候,方法簽名尤為重要。在傳遞複雜型別的時候相對較為麻煩,在實際使用中並不建議傳遞複雜物件。這裡說一下Java方法過載,在編寫native函式的時候不建議使用過載,過載的方法名稱很長,辨識度也不高。更建議在java中預處理過載的呼叫。例如:

public  class NativeFunc{
    public String Func(String str){
        return native_func1(str);
    }
    public String Func(int i){
        return native_func2(i);
    }

    public native String native_func1(String str);
    public native String native_func2(int i);
}

相關文章