您的位置:首页 > 其它

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

2015-09-22 11:11 525 查看
通常我们在编写的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解析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: