一.概述
Hello Everyone,上一篇是教大家配置了开发环境,好多同学对里面的某些代码可能还存在疑惑的,这篇文章就带领代驾熟悉下JNI编程的一些语法知识。
在开篇之前我们看下,我们以上篇的例子来展开
通过javah生成的c++文件,可以看到一个很奇怪的方法。这个就是我们今天的入门了。其中System.loadLibrary(“[库名]”),名字命名是有相应的规则的,而这个库名和CMakeList.txt文件中的库名必须一致。Java虚拟机会找到这个类名并调用该函数。
静态注册Native方法:
仔细看这个函数会发现方法名的组成为 Java_包名类名方法名。其中JNIEXPORT和JNICALL是两个宏定义,主要作用就是说明该函数为JNI函数,jstring是定义JNI定义的数据类型。在Java虚拟机加载的时候回连接对应的Native方法,比如在例子中的 public native String getId(); 通过javah操作后会生成
JNIEXPORT jstring JNICALL Java_com_example_mylibrary_JNITest_getID();
动态注册Native方法:
JNI_ONLOAD函数:该函数会有两个参数,其中*jvm为Java虚拟机实例,JavaVM结构体定义了以下函数:
DestroyJavaVM
AttachCurrentThread
DetachCurrentThread
GetEnv
这里我们使用了GetEnv函数获取JNIEnv变量,上面的JNI_ONLOAD函数有如下代码:
JNIEnv *env;
if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
这里调用了GetEnv函数获取JNIEnv结构体指针,其实JNIEnv结构体是指向一个函数表的,该函数表指向了一个对应的JNI函数,我们通过调用这些JNI函数实现JNI编程。
获取JAVA对象,完成动态注册
上面介绍 如何获取JNIEnv结构体指针,得到结构体指针我们就可以调用JNIEnv中的RegisterNatives函数完成动态注册native方法:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
第一个参数是Java层对应包含native方法的对象,指的就是JNITest对象了,通过调用JNIEnv对应的函数获取class对象
jclass clz = env->FindClass("com/github/songnick/jni/AndroidJni");
第二个参数是JNINativeMethod结构体指针,这里 JNINative结构体是描述Java层Native方法的,定义如下:
typedef struct {
const char* name;//Java层native方法的名字
const char* signature;//Java层native方法的描述符
void* fnPtr;//对应JNI函数的指针
} JNINativeMethod;
第三个参数为注册native方法的数量,一般会动态注册多个native方法,首先会定义一个JNINativeMethod数组,然后将该指针作为RegisterNative函数的参数传入
JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog}};
最后调用RegisterNative函数完成动态注册:
env->RegisterNatives(clz, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]));
JNIEnv结构体
JNIEnv结构体指向一个函数表,该函数表指向一系列的JNI函数,我们通过调用这些JNI函数可以与Java层进行交互,以下是常用的函数:
..........
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
CallVoidMethod(jobject obj, jmethodID methodID, ...)
CallBooleanMethod(jobject obj, jmethodID methodID, ...)
..........
- GetFieldID函数是获取Java对象中某个变量的ID
- GetBooleanField()函数是根据变量的ID获取数据类型为Boolean
- GetMethodID函数获取Java对象中对应的方法ID
- CallVoidMethod根据methodID调用对应对象中的方法,并且该方法的返回值Void类型
- CallBooleanMethod根据methodID调用对应对象中的方法,并且该方法的返回值Boolean类型
JNI数据类型
JNI的本质是衔接Java和C++/C 相互通信的,因为Java的对象是不被C++识别的,同样的C++的指针也不被Java识别,所以JNI需要自定义一些自己的数据类型来使双方的通信保持顺畅。
1.原始数据类型Java Type Native Typ Description
JavaType | Native | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
2.引用类型
前面我们在获取AndroidJni对象的使用通过定义jclass引用,然后调用FindClass函数获取了该对象,所以JNI也定义了一些引用类型以便JNI层调用,具体的引用类型如下:
jobject (all Java objects)
|
|-- jclass (java.lang.Class objects)
|-- jstring (java.lang.String objects)
|-- jarray (array)
| |--jobjectArray (object arrays)
| |--jbooleanArray (boolean arrays)
| |--jbyteArray (byte arrays)
| |--jcharArray (char arrays)
| |--jshortArray (short arrays)
| |--jintArray (int arrays)
| |--jlongArray (long arrays)
| |--jfloatArray (float arrays)
| |--jdoubleArray (double arrays)
|
|--jthrowable
3.方法和变量的ID
当需要调用Java中的某个方法的时候我们首先要获取它的ID,根据ID调用JNI函数获取该方法,变量的获取过程也是同样的过程,这些ID的结构体定义如下:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID *jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */
描述符
1.类描述符
前面为了获取Java的JNITest对象,是通过调用FindClass()函数获取的,该函数参数只有一个字符串参数,字符串如下:
>com/example/mylibrary/JNITest
这个就是我们JNI定义了对类的描述符,规则也很简单,包名+类名,并用“/”替换“.”
2.方法描述符
前面我们动态注册native方法的时候结构体JNINativeMethod中含有方法描述符,就是确定native方法的参数和返回值,
Method Descriptor | Java Language Type |
---|---|
"()Ljava/lang/String;" | String f(); |
"(ILjava/lang/Class;)J" | long f(int i, Class c); |
"([B)V" | String f(byte[] bytes); |
上面的栗子我们看到方法的返回类型和方法参数有引用类型以及boolean、int等基本数据类型,对于这些类型的描述符在下个部分介绍。这里数组的描述符以"["和对应的类型描述符来表述。对于二维数组以及三维数组则以"[["和"[[["表示:
Descriptor | Java Langauage Type |
---|---|
"[[I" | int[][] |
"[[[D" | double[][][] |
3.数据类型描述符
前面我们说了方法的描述符,那么针对boolean、int等数据类型描述符是怎样的呢,JNI对基本数据类型的描述符定义如下:
Descriptor | Java Langauage Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
Field Desciptor Java Language Type
Z boolean
B byte
C char
S short
I int
J long
F floa
D double
Descriptor | Java Langauage Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
对于引用类型描述符是以"L"开头";"结尾,示例如下所示:
Field Desciptor Java Language Type
"Ljava/lang/String;" String
"[Ljava/lang/Object;"
今天的学习就到这里了,该篇文章只是介绍了下基本的语法知识以及jni通信的基本方法。后续我们接着进行~