编写JNI的两种应用层与JNI层方法映射方式

来源:互联网 发布:java 首字母大写方法 编辑:程序博客网 时间:2024/06/05 23:00

通常我们在编写的JNI 时,在定义上层应用层需要调用的函数中,我们需要对该函数进行应用层与JNI层方法之间的映射。这样上层的Android应用程序才能正确的调用我们的JNI函数,这种映射的方式一共有两种。

在函数名中进行映射
在函数名中进行映射是最为简单的一种方法,因为只要我们知道调用我们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解析。

1 0
原创粉丝点击