zoukankan      html  css  js  c++  java
  • Android JNI的使用方法

    1、JNI是什么

    JNI是Java Native Interface的缩写,它提供若干的API实现Java与其他语言之间的通信。而Android Framework由基于Java语言的的Java层与基于C/C++语言的C/C++层组成,每个层中的功能模块都是以有相应的语言编写,并且两层中的大部分模块有着千丝万缕的联系。而在两层之间充当连接桥梁这一角色的就是JNI,它允许Java代码和C/C++编写的应用程序与库之间进行交互;通常在以下几种情况下使用JNI

    1、注重处理速度,C/C++的处理速度要优于Java语言

    2、硬件控制,硬件驱动程序通常使用C语言编写,而要是Java层能够控制硬件,需要用到JNI

    3、C/C++代码的复用,一些好的C/C++模块可以被多处复用

    2、在Java中调用C库函数

    下面以一个例子来说明在Java代码中调用C库函数的流程

    1、编写Java代码

    class HelloJNI {
        
        /*声明本地方法,该函数在C库中实现*/
        native void printHello();
        native void printString(String str);
        /*在静态块中加载C库,可以保证在main方法前加载完成*/
        static { System.loadLibrary("./hellojni"); }
        public static void main(String args[])
        {   
            HelloJNI myJNI = new HelloJNI();    
            /*调用C库中实现的函数*/
            myJNI.printHello();
            myJNI.printString("Hello world from printstring func");
        }   
    }

    在上述代码中使用native关键字声明本地方法,告诉Java编译器,此函数由其他语言编写;在静态块中加载hellojni库,该库由C语言实现,

    如果是在Linux系统下则会加载libhellojni.so,如果在Windows系统下则会加载hellojni.dll;(本文以Linux系统为测试环境)

    2、编译Java代码

    javac HelloJNI.java

    编译Java代码很简单,只要配置好JDK就可以完成编译,需要注意的是此时编译通过,但如果运行的话,由于没有实现本地函数,所以会抛出找不到函数的异常

    3、生成C头文件

    当Java调用本地函数printHello或者printString时并非直接映射到C语言的printHello或者printString函数,而是有一套自己的映射方法,使用如下命令即可生成C函数的头文件
    javap HelloJni
    执行完成后生成HelloJni.h如下
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class HelloJNI */
    #ifndef _Included_HelloJNI
    #define _Included_HelloJNI
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     HelloJNI
     * Method:    printHello
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *, jobject);
    /*
     * Class:     HelloJNI
     * Method:    printString
     * Signature: (Ljava/lang/String;)V
     */
    JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *, jobject, jstring);
    #ifdef __cplusplus
    }
    #endif
    #endif
    可以看到生成的函数原型并非与Java代码调用的函数一致,函数有JNIEXPORT和JNICALL两个关键字声明,这两个关键词是必须的,有了他们JNI才能正常调用函数;而通过观察函数名称,我们可以知道其命名方式是"Java_类名_本地方法名"; 再看参数,可知JNIEnv*和jobject是本地函数的共同参数,第一个参数是JNI接口的直接,用来调用JNI提供的基本函数集;第二个参数中保存着调用本地方法的对象的一个引用,上例中的jobject中保存的对象myJNI的引用,其他的参数根据Java代码的本地方法的调用生成的
    
    4、编写C/C++代码
    把上一步骤生成的HelloJni.h头文件include进来,实现其声明的函数即可,编写hellojni.c如下
    
    #include "HelloJNI.h"
    #include <stdio.h>
    /*
     * Class:     HelloJNI
     * Method:    printHello
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)
    {
        printf("Hello World!
    ");
        return;
    }
    /*
     * Class:     HelloJNI
     * Method:    printString
     * Signature: (Ljava/lang/String;)V
     */
    JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string)
    {
        /*JNI提供的基本函数集,将jstring转化成char *类型*/
        const char *str = (*env)->GetStringUTFChars(env, string, 0);
        printf("%s! 
    " ,str);
        return ;
    }
     

    5、生成C动态链接库

    gcc -fPIC -shared -o libhellojni.so hellojni.c -I$JAVA_HOME/include

    其中JAVA_HOME已经配置到环境变量中,表示JDK安装的目录,需要指定其中的include目录使用jni.h头文件

    6、运行Java程序

    此时执行java HelloJni会提供找不到hellojni库,这是由于在加载C库的时候在默认目录中没有找到libhellojni.so库,只需将该库复制到/usr/lib/下再次执行

    xlzh@cmos:~/code/jni/simpleJNI$ java HelloJNI 
    Hello World!
    Hello world from printstring func!

    3、调用JNI函数

    上图来自<Android框架揭秘>

    由上图可知此示例程序有JniFuncMain类、JniTest类和libjnifunc.so(linux系统)组成,此示例有Java和C代码混合而成。

    JniFuncMain类:

    public class JniFuncMain
    {
        private static int staticIntField = 300;
        /*加载libjnifunc.so库*/
        static { System.loadLibrary("jnifunc"); } 
        /*使用static关键字声明本地方法,再C库中实现*/
        public static native JniTest createJniObject();
        public static void main(String[] args) {
            System.out.println("[Java] createJniObject() call native method");    
            /*调用C库的createJniObject,得到JniTest对象,注意不是用new*/
            JniTest jniObj = createJniObject();
            /*利用JniTest对象调用JniTest中的方法*/
            jniObj.callTest();
        }
    }

    此例中与上例不同的是本地方法返回了一个JniTest类的对象的引用,这样就可以在JniFuncMain类中调用JniTest类的方法。

    JniTest类

    class JniTest {
        private int intField;
        public JniTest(int num)
        {   
            intField = num; 
            System.out.println("[Java] call JniTest: intFiled" + intField);
        }   
        public int callByNative(int num)
        {   
            System.out.println("[Java] JniTest 对象的 callByNative(" + num + ")调用");  
            return num;
        }   
        public void callTest()
        {   
            System.out.println("[Java] JniTest对象的callTest() 方法调用: intField = " + intField);  
        }   
    }

    JniTest类提供两个方法供JniFuncMain类和C库函数调用

    JniFuncMain.h

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class JniFuncMain */
    #ifndef _Included_JniFuncMain
    #define _Included_JniFuncMain
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     JniFuncMain
     * Method:    createJniObject
     * Signature: ()LJniTest;
     */
    JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
      (JNIEnv *, jclass);
    #ifdef __cplusplus
    }
    #endif
    #endif

    使用javah JniFuncMain生成头文件,需要注意的是第二个参数是jclass,而不是jobject,这是由于该本地方法在JniFuncMain类中声明的是static方法,所以第二个参数表示的该类的应用,而不需要对象的引用

    jnifunc.cpp
    
    #include "JniFuncMain.h"
    JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env, jclass clazz)
    {
        jclass targetClass;
        jmethodID mid;
        jobject newObject;
        jstring hellostr;
        jfieldID fid;
        jint staticIntField;
        jint result;
        /*获取JniFuncMain类的staticField变量值*/
        fid = env->GetStaticFieldID(clazz, "staticIntField", "I");
        staticIntField = env->GetStaticIntField(clazz, fid);
        printf("[CPP] 获取 JniFuncMain类的staticIntField 值
    ");
        printf("    JniFuncMain.staticIntField = %d
    ", staticIntField);
        /*查找生成对象的类*/
        targetClass = env->FindClass("JniTest");
        /*查找构造方法*/
        mid = env->GetMethodID(targetClass, "<init>", "(I)V");
        /*生成JniTest对象*/
        printf("[CPP] JniTest 对象生成 
    ");
        newObject = env->NewObject(targetClass, mid, 100);
        /*调用对象的方法*/
        mid = env->GetMethodID(targetClass, "callByNative", "(I)I");
        result = env->CallIntMethod(newObject, mid, 200);
        /*设置JniObject对象的intField值*/
        fid = env->GetFieldID(targetClass, "intField", "I");
        printf("[CPP] 设置JniTest对象的intField值为200
    ");
        env->SetIntField(newObject, fid, result);
        /*返回对象引用*/
        return newObject;
    }
    如果想在C代码中访问Java中的成员变量,就需要获取相应成员变量的ID值,成员变量的ID值保存在jfieldID类型的变量中;获取成员变量ID的JNI本地方法有两个,分别是
    
    /* 获取Java中的静态成员变量ID
     * env      : JNI接口指针
     * clazz    : 包含成员变量的类的jclass
     * name     : 成员变量名称
     * signature: 成员变量签名
     */
    jfield GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature);
    /*获取Java中的普通成员变量ID*/
    jfield GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature);
    上述示例中要访问Java类中的静态成员变量,所以需要使用GetStaticFieldID方法,其他参数很简单,直接使用即可,而对于变量的签名,则需要借助Java反编译器javap命令,如下所示
    
    xlzh@cmos:~/code/jni/middleJNI$ javap -s -p JniFuncMain
    Compiled from "JniFuncMain.java"
    public class JniFuncMain {
      private static int staticIntField;
        Signature: I
      public JniFuncMain();
        Signature: ()V
      public static native JniTest createJniObject();
        Signature: ()LJniTest;
      public static void main(java.lang.String[]);
        Signature: ([Ljava/lang/String;)V
      static {};
        Signature: ()V
    }

    可以看到,staticIntField的签名是I,将I传入第四个参数即可,其他函数中用到签名的时候可用同样的方法获取

    OK,我们得到了成员变量的ID,那么如何通过成员变量的ID来获取或者设置成员变量的值呢?就需要用到以下几个JNI函数

    /*
     * 获取Java类中静态成员变量的值
     * <jnitype> jobject,jboolean,jbyte,jchar, jshort, jint, jlong, jfloat, jdouble
     * <type>    Object ,Boolean ,Byte,Char  , Short , Int,  Long , Float , Double
     * env:      JNI接口指针
     * jcalss:   包含成员变量的类
     * jfieldID: 成员变量ID
     */
    <jnitype> GetStatic<type>Field(JNIEnv *env, jcalss jclazz, jfieldID fieldID)
    /*
     * 获取Java类的对象中普通成员变量的值
     */
    <jnitype> Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID)
    /*
     * 设置Java类中静态成员变量的值
     */
    <jnitype> SetStatic<type>Field(JNIEnv *env, jcalss clazz, jfieldID fieldID, <type> value)
    /*
     * 设置Java类的对象中普通成员变量的值
     */
    <jnitype> Set<type>Field(JNIEnv *env, jobject obj,  jfieldID fieldID, <type> value)
     
    与成员变量类似, 获取和调用类中方法的JNI函数原型如下
    /*获取Java类静态方法的ID*/
    jmethod GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature)
    /*获取Java类的对象中普通方法的ID*/
    jmethod GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature)
    /*调用Java类中的静态方法*/
     <jnitype> CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethod methodID, ...)
    /*调用Java类的对象中的普通方法*/
     <jnitype> Call<type>Method(JNIEnv *env, jobject obj, jmethod methodID, ...)
    如何获取Java类的对象呢?以示例中获取JniTest类的对象代码例,分为三步
    1、获取JniTest的类
    2、获取JniTest类的构造方法ID
    3、通过构造方法的ID调用和JniTest类使用NeoObject方法生成对象
    对比上例中获取JniTest的对象流程,可以很清楚的进行对照

    4、在C代码中运行Java类

    Java类编译的字节码需要在Java虚拟机上运行,那么在C/C++中运行Java类自然也需要加载Java虚拟机;JNI为我们提供了一套Invocation API,它允许本地代码在自身内存区域内加载Java虚拟机,同样我们以实例的方式进行讲解

    InvocationApiTest.java

    public class InvocationApiTest {
        public static void main(String[] args) {
            System.out.println(args[]0);
        }
    }

    invocationApi.c

    #include <jni.h>
    int main(void)
    {
        JNIEnv *env;
        JavaVM *vm;
        JavaVMInitArgs vm_args;
        JavaVMOption options[1];
        jint res;
        jclass cls;
        jmethodID mid;
        jstring jstr;
        jclass stringClass;
        jobjectArray args;
        /*加载虚拟机选项*/
        options[0].optionString = "-Djava.class.path=.";
        vm_args.version = JNI_VERSION_1_6;
        vm_args.options = options;
        vm_args.nOptions = 1;
        vm_args.ignoreUnrecognized = JNI_TRUE;
        /*生成虚拟机*/
        res = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args);
        /*查找并加载类*/
        cls = (*env)->FindClass(env, "InvocationApiTest");
        /*获取main()方法的ID*/
        mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
        /*生成字符串对象*/
        jstr = (*env)->NewStringUTF(env, "Hello Invocation API!!");
        stringClass = (*env)->FindClass(env, "java/lang/String");
        args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
        
        /*调用main()方法*/
        (*env)->CallStaticVoidMethod(env, cls, mid, args);
        /*销毁虚拟机*/
        (*vm)->DestroyJavaVM(vm);
    }
    编译允许结果如下
    xlzh@cmos:~/code/jni/superJNI$ javac InvocationApiTest.java 
    xlzh@cmos:~/code/jni/superJNI$ sudo echo "/usr/lib/jvm/java-1.7.0-openjdk-amd64/jre/lib/amd64/jamvm" >> /etc/ld.so.conf
    xlzh@cmos:~/code/jni/superJNI$ sudo ldconfig
    xlzh@cmos:~/code/jni/superJNI$ gcc -o a.out invocationApi.c -I$JAVA_HOME/include -L$JAVA_HOME/jre/lib/amd64/jamvm/ -ljvm
    xlzh@cmos:~/code/jni/superJNI$ ./a.out 
    Hello Invocation API!!
    xlzh@cmos:~/code/jni/superJNI$ 


  • 相关阅读:
    linux 权限管理命令
    大三上学期总结
    C# 读写Excel的一些方法,Aspose.Cells.dll
    Topshelf 创建.net服务整理和安装步骤(转)
    你必须知道的.NET之特性和属性(转)
    用SQL语句删除一个数据库的所有表和所有存储过程
    System.DllNotFoundException: 无法加载 DLL“FileTracker.dll”: 动态链接库(DLL)初始化例
    关于消息队列的使用[转]
    PhpStorm使用技巧小结
    转载]C#实现获取浏览器信息
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/10571817.html
Copyright © 2011-2022 走看看