JNI简单示例

来源:互联网 发布:淘宝卖家回复评价语 编辑:程序博客网 时间:2024/06/05 16:55
 

JNI简单示例

例子简介:

    这个简单的例子展示了上层应用程序如果利用JNI接口和底层驱动进行交互:上层应用通过两个按钮控制灯的开关。

步骤:

一、应用程序

    包含两个按钮,一个是开,一个是关,另外包含一个RadioButton指示灯的状态。

 

    关键代码:

package com.android.test;

 

public class LightControl extends Activity {

    @Override

    public void onCreate(Bundle savedInstanceState) {

        System.loadLibrary("light_control");//加载liblight_control.so动态库

        init_native();

    }

 

    //声明JNI方法

    private static native void light_control(int onoff);

    private static native int init_native();

    private static native int destroy_native();

}

二、JNI接口

    JNI层需要打开设备,并提供上层应用程序的读写接口,并在退出时释放占有的相关资源。由于HAL层是用C/C++语言编写的,而上层应用程序是由java语言编写的,因此中间层与上层程序之间沟通的桥梁,就是众所周知的JNI接口(JNI接口不是唯一的桥梁,采用socket等同样可以做到)。

 

    JNI需要完成如下工作:建立一个C/C++函数与Java函数映射的表,即JNINativeMethod表,对应上层应用中声明的JNI方法。

static JNINativeMethod method_table[] = {

    {"light_control","(I)V",(void*)light_control_native},

    { "init_native", "()I", (void*)init_native },

    { "destroy_native", "()I", (void*)destroy_native },

};

    为了理解上面表的含义,先来看看JNINativeMethod结构体的定义:

typedef struct {

    const char* name;

    const char* signature;

    void*       fnPtr;

} JNINativeMethod;

    name为指向java代码中函数的名称,fnPtr为本地函数指针,signature为描述函数参数和返回值类型的字符串,其中函数参数为java代码中的函数参数,本地函数的参数会多两个:JNIEnv *env和jobject clazz。

    从上面的method_table[]映射表可以看出,一共有三组函数映射,java代码中的light_control函数映射到本地函数light_control_native上,init_native函数映射到本地函数init_native上,destroy_native函数映射到本地函数destroy_native函数上。每个映射中的第二字符串,是函数参数和返回值类型的描述字符串,比如"(I)V",括号里面的每一个字符(或一组以L开头分号结束的字符串)都对应描述了函数的一个参数,紧跟在括号后面的字符(串)描述了函数的返回值类型。

    因此需要在本地代码中完成下面三个函数的定义:

    static void light_control_native(JNIEnv *env, jobject clazz, jint onoff)

    static jint init_native(JNIEnv *env, jobject clazz)

    static jint destroy_native(JNIEnv *env, jobject clazz)

    然后定义一个名为JNI_OnLoad的函数,注意该函数名必须为JNI_OnLoad,且需要用extern “C”声明为C语言方式编译和链接的函数(在源文件为c++文件时才需要),同时返回值类型必须为jint。

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

    ......//此处省略

    return JNI_VERSION_1_4;

}

    这些代码将被编译为动态库,比如例子中编译成了liblight_control.so动态库。那么上层程序可以通过System.loadLibrary("light_control")来加载该动态库。注意参数字符串是去掉”lib”和”.so”的库文件名称,同时需要确保liblight_control.so放置于/system/lib目录下。如果放置于其它地方,可以通过System.load(path)来加载,其中path为动态库文件的全路径名称。

    在动态库被加载后,会立即调用该库里面定义的JNI_OnLoad函数。因此需要在该函数中调用jniRegisterNativeMethods()来完成函数映射表在dalvik上的注册:

    jniRegisterNativeMethods(env, "com/android/test/LightControl",method_table, NELEM(method_table))

    env为dalvik的运行环境对象,字符串"com/android/test/LightControl"是java层的JNI接口类的包名和类名。method_table即为上面定义的函数映射表数组。

    在完成函数映射表数组的注册之后,java层程序就可以通过呼叫其JNI接口方法,来调用本地代码的JNI接口函数了。例如上面的例子中,在java层程序中通过调用light_control(onoff),就可以透过JNI接口调用本地代码的light_control_native(env,clazz, onoff)函数了。其中env和clazz参数是在dalvik环境调用本地代码函数时添加的两个参数,后面会分析其作用。

    最后需要写一个Android.mk来将这些代码编译成动态库。主要的编译设定为:

    LOCAL_SRC_FILES:= com_android_test_LightControl.cpp

    定义这次编译的源代码文件。

    LOCAL_SHARED_LIBRARIES := \

    libandroid_runtime \

    libcutils \

    libnativehelper

    定义在连接时相关的库,其中libcutils在用到LOGD等log信息打印函数的时候才需要。

    LOCAL_MODULE:= liblight_control

    定义生成的动态库的文件名为liblight_control.so。

 

三、底层驱动

    在light_init()初始化函数中:

int light_init(void)

{

    int ret;

       printk("init light control\n");

    ret = register_chrdev(LIGHT_CTL_MAJOR_NUM, DEV_NAME, &light_ctl_ops);

       light_ctl_class = class_create(THIS_MODULE, DEV_NAME);

       device_create(light_ctl_class, NULL, MKDEV(LIGHT_CTL_MAJOR_NUM, LIGHT_CTL_MINOR_NUM), NULL, DEV_NAME);

      

    if(ret < 0){

        printk("Fail to register the light control device! ret=%d\n", ret);

        return ret;

    }

    return 0;

}

    注册一个字符设备,其中DEV_NAME为"light_ctl",建立light_ctl_class的目的是为了在加载驱动后,会自动由udev在/dev目录下生成对应的设备文件:/dev/light_ctl。

对驱动的控制通过ioctl来完成:

static int light_status = 0;

int light_ctl_ioctl(struct inode *inode, struct file *file,

                 unsigned int cmd, unsigned long arg)

{

    int value;

      

    switch(cmd)

    {

           case LIGHT_CTL_SET:                    

                     if(copy_from_user(&value,(void *)arg,sizeof(int)))

                            return - EFAULT;

                     else

                            light_status = value;

               break;

              

              case LIGHT_CTL_GET:

                     value = light_status;

                     if(copy_to_user((void *)arg, &value, sizeof(int)))

                     break;

       

           default :

                 printk("Unsupported command!\n");

               break;

    }

 

    return 0;

}

    从ioctl函数中可以看出,在应用程序中,可以设置变量light_status的值,也可以读取其值。这些都是属于linux驱动设计的范畴,详细可以参考《Linux设备驱动程序》这本书。

原创粉丝点击