Andoid NDK编程 2 - JNI签名规则

上一篇文章中我们探讨了如何注册JNI的native函数,分为静态和动态两种注册方法。而在讨论静态注册的时候提到了要注意由javah生成的native函数的签名。今天我们就来深入的看看其签名规则。

JNI方法命名规则

首先是JNI方法的命名规则,也就是用javah生成的native函数看起来是什么样子的?

JNI方法名规范 :

返回值 + Java前缀 + 全路径类名 + 方法名 + 参数① JNIEnv + 参数② jobject + 其它参数;

一些注意事项:

  • 注意分隔符 : Java前缀 与 类名 以及类名之间的包名 和 方法名之间 使用 “_” 进行分割;

  • 如果在java中声明的是“静态”方法,则native方法也是static的。 否则不是。

  • 如果你的jni的native方法不是通过‘静态注册’的方法声明的,则不需要符合此规范,可根据自己习惯随意命名。(关于‘静态注册’与‘动态注册’请参考上一篇文章)

JNI方法签名规则

当java层调用jni函数的时候,JNI是如何识别Java方法的呢?

JNI依靠函数名和方法签名来识别方法, 函数名是不能唯一识别一个方法的, 因为方法可以重载, 类型签名包括了函数的参数和它的返回值;

而JNI函数的具体签名规则是:

(参数1类型签名参数2类型签名参数3类型签名参数N类型签名...)返回值类型签名

注意参数列表中没有任何间隔。

那么函数中的参数或者返回值类型在jni签名中该如何表示呢?请看下表:(注意 boolean 与 long 不是大写首字母, 分别是 Z 与 J, 类是L全限定类名, 数组是[元素类型签名,而其他类型则是其大写首字母)

字符 Java类型 C/C++类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble doube
F jfloat float
B jbyte byte
C jchar char
S jshort short

数组则以”[“开始,用两个字符表示:

字符 Java类型 C/C++类型
[Z jbooleanArray boolean[]
[I jintArray int[]
[J jlongArray long[]
[D jdoubleArray doube[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]

上面的都是基本类型,如果参数是Java类,则以”L”开头,以”;”结尾,中间是用”/“隔开包及类名,而其对应的C函数的参数则为jobject,一个例外是String类,它对应C类型jstring,例如:Ljava/lang /String; 、Ljava/net/Socket; 等,如果JAVA函数位于一个嵌入类(也被称为内部类),则用$作为类名间的分隔符,例如:

"Landroid/os/FileUtils$FileStatus;"

注意事项:

  • 类的签名规则:L + 全限定名 + ;三部分, 全限定类名以 / 分割;

    eg. long function(int n, String str, int[] arr);

    该方法的签名: (ILjava/lang/String;[I)J

接下来我们看几个具体的例子,来对JNI的签名有个更为直观的认识。

首先,在java代码中声明以下native函数:

private native long nativeFun1(long arg1, int arg2);
private native boolean nativeFun2(long arg1, long arg2);
private native float nativeFunc3(long arg1, int arg2, int arg3, int arg4);
static private native void  nativeFun4(long handle);
private static native long nativeFunc5(long arg1, ByteBuffer arg2);

那么这些函数在JNI层的函数映射表中对应的签名如下:

static JNINativeMethod native_functions_table[] = {
    {"nativeFun1", "(JI)J", (void*)Jni_fun1 },
    {"nativeFun2","(J)V",  (void*) Jni_fun2 },
    {"nativeFun3","(JIII)F",(void*) Jni_fun3},
    {"nativeFun4","(J)V", (void*) Jni_fun4 },
    {"nativeFun5", "(JLjava/nio/ByteBuffer;)J", (void *) Jni_fun5 }
}

JNI函数的参数

讲完了函数的签名规则,最后我们来看看函数里面的参数。

细心的你可能已经从你的jni函数的参数里面发现了:每个函数的前两个参数都默认加上的,也就是即便你的java函数中声明的native函数没有参数,但是对应生成的jni函数里面也有两个参数:JNIEnv* 以及 jobject。接下来说一说这俩参数是做什么用的:

  • JNIEnv: 该参数代表Java环境, 通过这个环境可以调用Java中的方法; 这些函数可以在jni.h中查询到,通过这些函数可以实现 Java层 与 JNI层的交互 , 通过JNIEnv 调用JNI函数 可以访问java虚拟机, 操作java对象;

  • jobject: 该参数代表调用jni方法的Java类或者对象, 如果Native方法是非静态的, 那么第这个参数就是对Java对象的引用, 如果Native方法是静态的, 那么这个参数就是对Java类的Class对象的引用;

另外关于JNIEnv,还有一些注意事项:

  • JNI线程相关性 : JNIEnv只在当前的线程有效,JNIEnv不能跨线程传递, 相同的Java线程调用本地方法, 所使用的JNIEnv是相同的, 一个Native方法不能被不同的Java线程调用;

  • JNIEnv结构体系 : JNIEnv指针指向一个线程相关的结构,线程相关结构指向一个指针数组,指针数组中的每个元素最终指向一个JNI函数.

后续

JNI的签名相关的内容就到了,接下来我们会继续学习JNI,讨论关于JNI与java交互的具体实现方法。