Register native method - 數據類型和method descriptor
使用JNI時,為了使得虛擬機可以找到在C/C++ code中定義的native方法,有兩種機制可以用,一種是通過為native 方法以特定格式命名來實現,另外的一種是所謂的JNI_OnLoad機制。更多信息,可參考《android app中使用JNI》。在JNI_OnLoad機制中,我們需要創建一個映射表,以描述java code中聲明的native 方法,與C/C++ code中的實現函數之間的對應關系。如下面的這樣:
static JNINativeMethod gMethods[] = { NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"), NATIVE_METHOD(HelloJni, stringToJNI, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(HelloJni, sumIntWithNative, "([III)I"), NATIVE_METHOD(HelloJni, sumDoubleWithNative, "([DII)D"), };
可以看到,這個映射表是一個數組,它的數據元素類型為JNINativeMethod,這個結構的定義如下:
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;
這個結構有三個成員,name為java code中聲明的方法名,fnPtr為函數指針,指向實現了java 的native方法的那個C/C++函數,signature則為方法的簽名。name 和fnPtr的含義都清晰明確。而方法的簽名究竟是干什么的呢?看上面的gMethods的定義,想必我們大概也可以猜個八九不離十。沒錯,方法的簽名就是用來描述一個方法,它所接受的參數類型,及其返回值類型的。
這個signature,又稱為method descriptor。它的定義,究竟又有什么樣的規則呢?JNI的Spec中對於這個問題,有如下的一段描述:
- Method descriptors are formed by placing the field descriptors of all argument types in a pair of parentheses, and following that by the field descriptor of the return type. There are no spaces or other separator characters between the argument types. "V" is used to denote the void method return type. Constructors use "V" as their return type, and use "<init>" as their name.
意思就是說,method descriptor由兩個部分組成,一是由小括號包裹的,所有參數類型的field descriptors所組成的部分,其中的這些field descriptors對應於參數的順序依次排列;二是緊緊跟着的返回值類型的field descriptors。整個的method descriptor中都不包含空格。“V”用於表示void 方法的返回值類型。構造函數用“V”作為他們的返回值類型,並使用“<init>”作為他們的名字。而不接受任何參數的方法,其參數的field descriptors部分則為空,而只留小括號在那里。
接下來我們可以看一下,各種數據類型,它的field descriptors都是些什么鬼東西。首先,可以先來看一下Java的原始數據類型的field descriptors:

而對於引用類型,其field descriptor則是以“L”字符開頭,后面緊跟着class descriptor,並以“;”字符結尾。一個class descriptor用表示一個類或接口的名稱。我們可以從一個類或接口的完全限定名中獲取到這個class descriptor,方法為,將完全限定名字串中的“.”字符都用“/”字符來替換,比如java.lang.String 這個類的class descriptor就是"java/lang/String"。數組當然也是引用類型。數組的field descriptor的構成則為“[”字符,后面緊跟着數組成員類型的field descriptor。比如,“int[]”的class descriptor為 "[I", “double[][][]” 的class descriptor為"[[[D"。回到field descriptor,原始數據類型的數組,它們的field descriptor不同於其他的引用類型的地方則在於,其是不需要開頭的“L”及后面的“;”字符的。像下面這樣:

看一些實際code的例子。首先,是Java code中的native方法的聲明:
public native int sumIntWithNative(int[] dataElement, int start, int end); public native double sumDoubleWithNative (double[] dataElement, int start, int end); public native String stringToJNI(String text);
然后是native code中,我們所創建的映射表,值得我們重點關注的,是那些個方法的簽名與Java code中native 方法的聲明中參數及返回值類型之間的對應關系:
static JNINativeMethod gMethods[] = { NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"), NATIVE_METHOD(HelloJni, stringToJNI, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(HelloJni, sumIntWithNative, "([III)I"), NATIVE_METHOD(HelloJni, sumDoubleWithNative, "([DII)D"), };
在Java code中,對於那些native方法的調用:
int[] dataElement = new int[]{2, 3, 4, 6}; int sum = sumIntWithNative(dataElement, 0, dataElement.length); double[] doubleElement = new double[]{3.4, 5.3, 7.6, 9.2}; double doubleSum = sumDoubleWithNative(doubleElement, 0, doubleElement.length); stringToJNI("text");
native code中,對於那些native方法的實現:
static jstring HelloJni_stringToJNI( JNIEnv* env, jobject thiz, jstring text) { JniDebug("from HelloJni_stringToJNI"); return text; } static jint HelloJni_sumIntWithNative( JNIEnv* env, jobject thiz, jintArray dataElement, jint start, jint end) { JniDebug("from HelloJni_sumIntWithNative"); } static jdouble HelloJni_sumDoubleWithNative( JNIEnv* env, jobject thiz, jdoubleArray dataElement, jint start, jint end) { JniDebug("from HelloJni_sumDoubleWithNative"); }
在native 方法的實現中,值得我們關注的是,那些參數的聲明。可以看到,Java Language Type用於java code中,native方法聲明時描述參數或返回值的類型;field descriptor 用於method descriptor,或稱signature中,描述參數或返回值的類型;而Native Type則用於native method 聲明中,描述參數或返回值的類型。上面的那段code執行的結果:

此外,android frameworks中JNI code的一些例子,也值得我們參考,如JellyBean/frameworks/base/core/jni/android/graphics/Canvas.cpp這個文件里面的映射表:
{"native_drawPaint","(II)V", (void*) SkCanvasGlue::drawPaint},
{"drawPoint", "(FFLandroid/graphics/Paint;)V",(void*) SkCanvasGlue::drawPoint},
{"drawPoints", "([FIILandroid/graphics/Paint;)V",(void*) SkCanvasGlue::drawPoints},
{"drawLines", "([FIILandroid/graphics/Paint;)V",(void*) SkCanvasGlue::drawLines},
{"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
{"native_drawRect","(ILandroid/graphics/RectF;I)V",(void*) SkCanvasGlue::drawRect__RectFPaint},
{"native_drawRect","(IFFFFI)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
{"native_drawOval","(ILandroid/graphics/RectF;I)V",(void*) SkCanvasGlue::drawOval},
{"native_drawCircle","(IFFFI)V", (void*) SkCanvasGlue::drawCircle},
{"native_drawArc","(ILandroid/graphics/RectF;FFZI)V",(void*) SkCanvasGlue::drawArc},
{"native_drawRoundRect","(ILandroid/graphics/RectF;FFI)V",(void*) SkCanvasGlue::drawRoundRect},
{"native_drawPath","(III)V", (void*) SkCanvasGlue::drawPath},
{"native_drawBitmap","(IIFFIIII)V",(void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
{"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/RectF;III)V",(void*) SkCanvasGlue::drawBitmapRF},
{"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/Rect;III)V",
這個表中,非java 語言內部類的field descriptor,如android.graphics.Paint, android.graphics.RectF等的field descriptor的寫法,值得我們參考借鑒。
【轉】:https://my.oschina.net/wolfcs/blog/126474
