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 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的写法,值得我们参考借鉴。
Done.