http://zhixinliu.com/2015/07/02/2015-07-02-jni-rule/
在上一篇文章中我们探讨了如何注册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交互的具体实现方法。