JNI 开发之方法签名与 Java 通信(二),mongodb 入门篇
对于数组,其为 : [ + 其类型的域描述符 + ;
int[ ] 其描述符为[Ifloat[ ] 其描述符为[FString[ ] 其描述符为[Ljava/lang/String;Object[ ]类型的域描述符为[Ljava/lang/Object;
多维数组则是 n 个[ +该类型的域描述符 , N 代表的是几维数组。例如:
int [ ][ ] 其描述符为[[Ifloat[ ][ ] 其描述符为[[F
方法描述符
将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符,规则如下: (参数的域描述符的叠加)返回类型描述符。对于,没有返回值的,用 V(表示 void 型)表示。举例如下:
Java 层方法 JNI 函数签名
String test ( ) Ljava/lang/String;
int f (int i, Object object) (ILjava/lang/Object;)I
void set (byte[ ] bytes) ([B)V
二、JNI 实现 java 与 c/c++相互通讯
2.1、签名映射表
JNI 获取 Java 类的方法和字段,都需要一个很重要的参数,就是 Java 类的方法和字段的签名。所以最好能够记住它们。
"Lfully-qualified-class;"->L 类全名; 例如 Java String 类对应的签名是 Ljava/lang/String;
"[type"->java 数组的签名,例如 int[]的签名[I,java Stringg[]的签名是[Ljava/lang/String;
"(arg-types)ret-type"->(函数参数)返回值,()只是所有参数,ret-type 是返回类型签名例如?
void test(String msg)对应的签名是(Ljava/lang/String;)V
long f(int n,String s,int[] arr)对应的签名(Ijava/lang/String;[I)J
void f()对应用的签名()V
2.2、jni 是如何访问 java 中的方法和字段
jni 的 native 接口中,第一个参数为 JNI 接口指针(JNIEnv),第二个参数根据 native 方法是静态还是非静态而不同。非静态 native 方法的第二个参数是对该对象的引用,静态方法的第二参数是对其 java 类的引用。其与参数对应 Java 方法参数。
从上述描述中,如果是非静态的,我们可以拿到对象的引用。通过对象的引用,我们可以访问该对象的字段和方法。如果是静态的,我们可以访问该对象的静态方法和静态字段。那么 jni 中具体是如何访问的呢?我们将在下面的章节通过实例来介绍
2.3、jni 访问 java 中的方法
java 代码
public void show(String s){Log.i("MainActivity","show:"+s);}public native void showString(String s);
jni 代码
//访问 Java 中的 show 方法 JNIEXPORT void JNICALL Java_com_zzy_ndkdemo_MainActivity_showString(JNIEnv *env, jobject instance, jstring s) {
//获取 instance 的类名称 jclass cls = (*env)->GetObjectClass(env,instance);if(cls==NULL){LOGD("Class %s not found");}
//获取方法 ID,第二个参数为类名称,第三个参数为方法名称,第三个参数为方法签名,详细参见签名对照表 jmethodID id =(*env)->GetMethodID(env,cls,"show","(Ljava/lang/String;)V");if(id !=NULL){//访问方法,第二个为类实例,第三个参数为方法 ID,第四和第四以后为方法参数,// 根据返回类型不同,调用不同的 CallXXXMethod 方法,xxx 返回类型(*env)->CallVoidMethod(env,instance,id,s);}}
2.4、jni 访问 java 中的静态方法
java 代码
public static void showStatic(String s){Log.i("MainActivity","show static:"+s);}public native void showStaticString(String s);
jni 代码
//访问 Java 中的 showStatic 静态方法 JNIEXPORT void JNICALL Java_com_zzy_ndkdemo_MainActivity_showStaticString(JNIEnv *env, jobject instance, jstring s) {
//获取 instance 的类名称 jclass cls = (*env)->GetObjectClass(env,instance);if(cls==NULL){LOGD("Class %s not found");}
//获取静态方法 ID,第二个参数为类名称,第三个参数为方法名称,第三个参数为方法签名,详细参见签名对照表 jmethodID id =(*env)->GetStaticMethodID(env,cls,"showStatic","(Ljava/lang/String;)V");if(id !=NULL){//访问方法,第二个为类名称,第三个参数为方法 ID,第四和第四以后为方法参数,// 根据返回类型不同,调用不同的 CallStaticXXXMethod 方法,xxx 返回类型(*env)->CallStaticVoidMethod(env,cls,id,s);}}
1、从中我们可以看出静态方法比非静态方法的访问多加了一个 static,访问函数由类实例,变成类引用。
2、如果静态方法不存在 instance 类中,我们可以通过 FindClass 进行访问,其中第二个参数为类的全路径?? ?? ?
jclass cls = (*env)->FindClass(env, "com/zzy/ndkdemo/JniDemo");if (cls == NULL) {LOGD("Class %s not found");}
2.5、jni 访问 java 中的字段
java 代码
User user = new User();user.name = "zhang san";user.age = 30;User.token = "2018-2011—3223";
String name = showUserName(user);Log.i("MainActivity","show name:"+name);public native String showUserName(User user);
jni 代码
//访问 User 中的字段 nameJNIEXPORT jstring JNICALL Java_com_zzy_ndkdemo_MainActivity_showUserName(JNIEnv *env, jobject instance, jobject user) {
//获取 User 的类名称 jclass cls=(*env)->GetObjectClass(env,user);if (cls == NULL) {LOGD("Class %s not found");return NULL;}
//获取字段 ID,第二个参数类名称,第三个参数字段名,第三个参数字段签名,详细参见签名对照表 jfieldID id = (*env)->GetFieldID(env,cls,"name","Ljava/lang/String;");if(id==NULL){LOGD("Field token not found");return NULL;}//获取字段内容,调用 GetXXXField,xxx 为字段类型。除基本类型外,其他的都使用 GetObjectFieldjstring name=(jstring) (*env)->GetObjectField(env,user,id);return name;}
2.6、jni 访问 Java 中的静态字段
java 代码
User user = new User();user.name = "zhang san";user.age = 30;User.token = "2018-2011—3223";String token =showUserStaticToken(user);Log.i("MainActivity","show static token:"+token);public native String showUserStaticToken(User user);
jni 代码
//访问 User 中的静态字段 TokenJNIEXPORT jstring JNICALL Java_com_zzy_ndkdemo_MainActivity_showUserStaticToken(JNIEnv *env, jobject instance, jobject user) {
//获取 User 的类名称 jclass cls=(*env)->GetObjectClass(env,user);if (cls == NULL) {LOGD("Class %s not found");return NULL;}
//获取静态字段 ID,第二个参数类名称,第三个参数字段名,第三个参数字段签名,
详细参见签名对照表 jfieldID id = (*env)->GetStaticFieldID(env,cls,"token","Ljava/lang/String;");if(id==NULL){LOGD("Field token not found");return NULL;}
//获取字段内容,调用 GetStaticXXXField,xxx 为字段类型。除基本类型外,其他的都使用 GetStaticObjectFieldjstring token=(*env)->GetStaticObjectField(env,cls,id);return token;}
1、从中我们可以看出静态字段比非静态字段的访问多加了一个 static,访问函数由类实例,变成类引用。
2、如果静态字段中不存在 instance 类中,我们可以通过 FindClass 进行访问,其中第二个参数为类的全路径 ? ?
jclass cls = (*env)->FindClass(env, "com/zzy/ndkdemo/User");if (cls == NULL) {LOGD("Class %s not found");}
2.7、jni 中更新 java 的字段内容
java 代码
User user = updateUser(user);Log.i("MainActivity","updateUser name:"+user.name+" age:"+user.age+" token:"+User.token);public native User updateUser(User user);
jni 代码
//更新 user 内容 JNIEXPORT jobject JNICALL Java_com_zzy_ndkdemo_MainActivity_updateUser(JNIEnv *env, jobject instance,jobject user){//获取 User 的类名称 jclass cls=(*env)->GetObjectClass(env,user);if (cls == NULL) {LOGD("Class %s not found");return NULL;}
//获取每一个字段 ID,第二个参数类名称,第三个参数字段名,第三个参数字段签名,详细参见签名对照表 jfieldID idName = (*env)->GetFieldID(env,cls,"name","Ljava/lang/String;");jfieldID idAge = (*env)->GetFieldID(env,cls,"age","I");jfieldID idToken = (*env)->GetStaticFieldID(env,cls,"token","Ljava/lang/String;");
//将 cha 字符串转成 jstringjstring name = (*env)->NewStringUTF(env, "李四");jstring token = (*env)->NewStringUTF(env, "new token");
//更新字段内容,调用 setxxxField,xxx 为字段类型,如果是静态字段还需要加上 static//第二个参数为需要修改的类实例或则类名,第三个参数为字段 Id,第四个参数为需要修改的内容(*env)->SetObjectField(env,user,idName,name);(*env)->SetIntField(env,user,idAge,20);(*env)->SetStaticObjectField(env,cls,idToken,token);
return user;}
2.8、jni 中创建 java 类实例
java 代码
User user = createUser();Log.i("MainActivity","createUser name:"+user.name+" age:"+user.age+" token:"+User.token);public native User createUser();
jni 代码
//创建 user 实例 JNIEXPORT jobject JNICALL Java_com_zzy_ndkdemo_MainActivity_createUser(JNIEnv *env, jobject instance){//在指定路径查找到 USer 类名称 jclass cls = (*env)->FindClass(env, "com/zzy/ndkdemo/User");if (cls == NULL) {LOGD("Class %s not found");}
//获取 User 类的构造方法 ID,第二个参数为类名称,第三个参数固定为"<init>"//第四个参数为构造函数签名,详细参见签名对照表 jmethodID id= (*env)->GetMethodID(env, cls, "<init>", "()V");
//实例化 User 类,第二个参数类名称,第三个参数构造方法 IDjobject user = (*env)->NewObject(env, cls, id);if (user == NULL) {LOGD("Create User failed");}
//获取每一个字段 ID,第二个参数类名称,第三个参数字段名,第三个参数字段签名,详细参见签名对照表 jfieldID idName = (*env)->GetFieldID(env,cls,"name","Ljava/lang/String;");jfieldID idAge = (*env)->GetFieldID(env,cls,"age","I");jfieldID idToken = (*env)->GetStaticFieldID(env,cls,"token","Ljava/lang/String;");
//将 cha 字符串转成 jstringjstring name = (*env)->NewStringUTF(env, "王五");jstring token = (*env)->NewStringUTF(env, "second token");
//赋予字段内容,调用 setxxxField,xxx 为字段类型,如果是静态字段还需要加上 static//第二个参数为需要修改的类实例或则类名,第三个参数为字段 Id,第四个参数为需要修改的内容(*env)->SetObjectField(env,user,idName,name);(*env)->SetIntField(env,user,idAge,10);(*env)->SetStaticObjectField(env,cls,idToken,token);
return user;}
2.9、jni 中的异常
JNI 中也有异常,不过它和 C++、Java 的异常不太一样。当调用 JNIEnv 的某些函数出错后,会产生一个异常,但这个异常不会中断本地函数的执行,直到从 JNI 层返回到 Java 层后,虚拟机才会抛出这个异常。虽然在 JNI 层中产生的异常不会中断本地函数的运行,但一旦产生异常后,就只能做一些资源清理工作了(例如释放全局引用,或者 ReleaseStringChars)。如果这时调用除上面所说函数之外的其他 JNIEnv 函数,则会导致程序死掉。JNIEnv 提供了三个函数进行帮助:
1、ExceptionOccured 函数,用来判断是否发生异常。
2、ExceptionClear 函数,用来清理当前 JNI 层中发生的异常。
3、ThrowNew 函数,用来向 Java 层抛出异常。??
int jniCheckException(JNIEnv *env) {jthrowable ex = (*env)->ExceptionOccurred(env);if (ex) {(*env)->ExceptionDescribe(env);(*env)->ExceptionClear(env);(*env)->DeleteLocalRef(env, ex);return 1;}return 0;
评论