编写JNI的两种应用层与JNI层方法映射方式
2015-09-22 11:11
525 查看
通常我们在编写的JNI 时,在定义上层应用层需要调用的函数中,我们需要对该函数进行应用层与JNI层方法之间的映射。这样上层的Android应用程序才能正确的调用我们的JNI函数,这种映射的方式一共有两种。
在函数名中进行映射
在函数名中进行映射是最为简单的一种方法,因为只要我们知道调用我们JNI函数的Java文件所在的路径,那么我们就将该路径放在我们JNI对应函数的前面就可以了,同时还要在函数前面加上Java,路径间用”_”进行分隔。例如,如果我们的应用程序中这样加载动态链接库:
由上面的应用程序我们知道该java文件放在com/intel/jni包中,同时文件名为CC1100DataSource,那么我们在编写相应的jni函数时就应该这样编写,这里以open函数为例:
可以看到我们的函数名为Java_com_intel_jni_CC1100DataSource_Open,在open前面分别有Java和Java文件所处的路径。这样编写之后,我们的jni就和上层调用的Java程序就建立好连接了。
采用注册本地方法的方式
这种方式相对于前一种在编写的时候要稍微复杂一点,但是定义好了之后,会更加灵活。在我们实际的开发过程中会更加倾向该种方式。该种方式在定义的时候主要分为三步:
第一步:建立映射连接
建立映射连接需要使用到JNINativeMethod结构体类型的数组,JNINativeMethod类型的数据主要由三个部分组成,第一个是定义上层应用程序的调用函数的名,第二个是定义函数返回类型和参数类型。第四个是其对应的JNI函数名。
下面就以我们通用的open、read、write、close函数作为例子来进行映射。
上面建立了一个JNINativeMethod类型的数组,其中open函数为返回值为int类型,无参数。read函数参数为int类型数据,返回值类型为byte数组。关于这种表示方式可以参照博文Android JNI 使用的数据结构JNINativeMethod详解。
第二步:采用RegisterNatives注册第一步所建立好的映射
在这一步中我们要将之前建立的函数映射注册进动态链接库文件,同时由于在上一步建立映射的过程中我们并没有定义好上层调用的Java文件的路径和文件名,所以在注册的时候我们也要一一定义。
这里我还是以自己编写的一个radar驱动程序为例:
从上面我们也可以体会到,每次当我们需要更改上层调用的java文件的路径或者是文件名时,只需要更改kClassName所指定的路径就行,而不需要像第一种方式那样要将所有与之映射的文件的JNI函数全部都更改名字。
第三步:将上一步的注册函数放入JNI_OnLoad方法当中
注意这个JNI_OnLoad方法属于JNI的系统方法,当我们在上层java程序中调用System.loadLibrary(“cc1100”);时就会首先去调用JNI文件里面的JNI_OnLoad函数。平时如果我们没有写JNI_Onload方法,则表示JNI_Onload方法为空。
需要注意的是对于JNI文件,我们既可以采用C++的语言编写,也可以采用C语言编写,如果采用C语言编写,可能在有些语法规则上面会有一些细微区别。例如我们在使用env的时候,如果是C++语言,则为:env->RegisterNatives,而如果是C语言的话,便是(*env)->RegisterNatives。网上有一篇关于JNIEnv的介绍JNIEnv解析。
在函数名中进行映射
在函数名中进行映射是最为简单的一种方法,因为只要我们知道调用我们JNI函数的Java文件所在的路径,那么我们就将该路径放在我们JNI对应函数的前面就可以了,同时还要在函数前面加上Java,路径间用”_”进行分隔。例如,如果我们的应用程序中这样加载动态链接库:
package com.intel.jni; public class CC1100DataSource { public native int Open(); public native int Close(); public native byte[] Read( int len); public native int Write(char[] buf , int len); public static CC1100DataSource cc1100instance; public static CC1100DataSource getCC1100DataSource() { cc1100instance = new CC1100DataSource(); return cc1100instance; } static { System.loadLibrary("cc1100"); } }
由上面的应用程序我们知道该java文件放在com/intel/jni包中,同时文件名为CC1100DataSource,那么我们在编写相应的jni函数时就应该这样编写,这里以open函数为例:
/* * Class: cc1100 * Method: Open * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_intel_jni_CC1100DataSource_Open (JNIEnv *env, jobject obj) { if(fd<=0)fd = open("/dev/cc1100", O_RDWR|O_NDELAY|O_NOCTTY); if(fd <=0 )__android_log_print(ANDROID_LOG_ERROR, "serial", "open /dev/cc1100 Error"); else __android_log_print(ANDROID_LOG_INFO, "serial", "open /dev/cc1100 Sucess fd=%d\n",fd); }
可以看到我们的函数名为Java_com_intel_jni_CC1100DataSource_Open,在open前面分别有Java和Java文件所处的路径。这样编写之后,我们的jni就和上层调用的Java程序就建立好连接了。
采用注册本地方法的方式
这种方式相对于前一种在编写的时候要稍微复杂一点,但是定义好了之后,会更加灵活。在我们实际的开发过程中会更加倾向该种方式。该种方式在定义的时候主要分为三步:
第一步:建立映射连接
建立映射连接需要使用到JNINativeMethod结构体类型的数组,JNINativeMethod类型的数据主要由三个部分组成,第一个是定义上层应用程序的调用函数的名,第二个是定义函数返回类型和参数类型。第四个是其对应的JNI函数名。
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;
下面就以我们通用的open、read、write、close函数作为例子来进行映射。
static const JNINativeMethod methods[] = { {"_open", "()I", (void *) Radar_Open}, {"_read", "(I)[B", (void *) Radar_Read}, {"_write", "(C)I", (void *) Radar_Write}, {"_open", "()I", (void *) Radar_Open}, };
上面建立了一个JNINativeMethod类型的数组,其中open函数为返回值为int类型,无参数。read函数参数为int类型数据,返回值类型为byte数组。关于这种表示方式可以参照博文Android JNI 使用的数据结构JNINativeMethod详解。
第二步:采用RegisterNatives注册第一步所建立好的映射
在这一步中我们要将之前建立的函数映射注册进动态链接库文件,同时由于在上一步建立映射的过程中我们并没有定义好上层调用的Java文件的路径和文件名,所以在注册的时候我们也要一一定义。
这里我还是以自己编写的一个radar驱动程序为例:
int register_radar_jni(JNIEnv* env) { static const char* const kClassName = "com/intel/jni/radar"; jclass clazz; clazz = (*env)->FindClass(env,kClassName); if(clazz == NULL) { LOGE("Can't find class %s\n", kClassName); return -1; } if((*env)->RegisterNatives(env,clazz, methods, sizeof(methods)/sizeof(methods[0]))!= JNI_OK) { LOGE("Failed registering methods for %s\n", kClassName); return -1; } return 0; }
从上面我们也可以体会到,每次当我们需要更改上层调用的java文件的路径或者是文件名时,只需要更改kClassName所指定的路径就行,而不需要像第一种方式那样要将所有与之映射的文件的JNI函数全部都更改名字。
第三步:将上一步的注册函数放入JNI_OnLoad方法当中
注意这个JNI_OnLoad方法属于JNI的系统方法,当我们在上层java程序中调用System.loadLibrary(“cc1100”);时就会首先去调用JNI文件里面的JNI_OnLoad函数。平时如果我们没有写JNI_Onload方法,则表示JNI_Onload方法为空。
jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if((*vm)->GetEnv(vm,(void **) &env, JNI_VERSION_1_4)!=JNI_OK) { LOGE("GetEnv failed!!!!!!!!!!!!!!"); return result; } register_radar_jni(env); return JNI_VERSION_1_4; }
需要注意的是对于JNI文件,我们既可以采用C++的语言编写,也可以采用C语言编写,如果采用C语言编写,可能在有些语法规则上面会有一些细微区别。例如我们在使用env的时候,如果是C++语言,则为:env->RegisterNatives,而如果是C语言的话,便是(*env)->RegisterNatives。网上有一篇关于JNIEnv的介绍JNIEnv解析。
相关文章推荐
- position:sticky实现iOS6+下的粘性布局
- mediawiki系列1:皮肤的自定义
- PHP面试总结
- LeetCode (13): 3Sum Closest
- Linux下C高手成长过程----经典书籍推荐
- MapReduce V2---Yarn的架构及其执行原理
- HTML5基础之canvas绘图(一)
- SQL Server ROWLOCK行级锁
- getopt— C-style parser for command line options
- WebService接口
- mysql my.ini配置
- Maven简介(二)——使用Maven
- Ubuntu Server 添加/删除/修改用户
- Android中属性动画解析
- 有了IP和MAC 为什么还需要ARP协议?
- 省市联动 DropDownList控件
- MongoDB安装
- C#web服务器
- Android保存List<Map<String,Object>>数据到SD卡及读取
- Ubuntu Server9 + Tomcat + mysql + Jdk 安装后遇到的一些常见问题