• JNI开发流程-JNI/NDK【转】


    本文转载自:http://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/workflow.html

    开发流程

    JNI 全称是 Java Native Interface(Java 本地接口)单词首字母的缩写,本地接口就是指用 C 和 C++ 开发的接口。由于 JNI 是 JVM 规范中的一部份,因此可以将我们写的 JNI 程序在任何实现了 JNI 规范的 Java 虚拟机中运行。同时,这个特性使我们可以复用以前用 C/C++ 写的大量代码。

    开发 JNI 程序会受到系统环境的限制,因为用 C/C++ 语言写出来的代码或模块,编译过程当中要依赖当前操作系统环境所提供的一些库函数,并和本地库链接在一起。而且编译后生成的二进制代码只能在本地操作系统环境下运行,因为不同的操作系统环境,有自己的本地库和 CPU 指令集,而且各个平台对标准 C/C++ 的规范和标准库函数实现方式也有所区别。这就造成使用了 JNI 接口的 JAVA 程序,不再像以前那样自由的跨平台。如果要实现跨平台,就必须将本地代码在不同的操作系统平台下编译出相应的动态库。

    JNI 开发流程主要分为以下 6 步:

    • 编写声明了 native 方法的 Java 类
    • 将 Java 源代码编译成 class 字节码文件
    • 用 javah -jni 命令生成.h头文件(javah 是 jdk 自带的一个命令,-jni 参数表示将 class 中用native 声明的函数生成 JNI 规则的函数)
    • 用本地代码实现.h头文件中的函数
    • 将本地代码编译成动态库(Windows:*.dll,linux/unix:*.so,mac os x:*.jnilib)
    • 拷贝动态库至 java.library.path 本地库搜索目录下,并运行 Java 程序

    通过上面的介绍,相信大家对 JNI 及开发流程有了一个整体的认识,下面通过一个 HelloWorld 的示例,再深入了解 JNI 开发的各个环节及注意事项。

    HelloWorld

    注意:这个案例用命令行的方式介绍开发流程,这样大家对 JNI 开发流程的印象会更加深刻,后面的案例都采用eclipse+cdt 来开发。

    第一步,新建一个 HelloWorld.java 源文件

    
    public class HelloWorld {
    
      public class HelloWorld {
    
        public static native String sayHello(String name); // 1.声明这是一个native函数,由本地代码实现
    
        public static void main(String[] args) {
            String text = sayHello("yangxin");  // 3.调用本地函数
            System.out.println(text);
        }
    
        static {
            System.loadLibrary("HelloWorld");   // 2.加载实现了native函数的动态库,只需要写动态库的名字
        }
    
    }

    第二步,用 javac 命令将.java源文件编译成.class字节码文件

    注意:HelloWorld 放在 com.study.jnilearn 包下面

    javac src/com/study/jnilearn/HelloWorld.java -d ./bin  

    -d 表示将编译后的 class 文件放到指定的目录下,这里我把它放到和 src 同级的 bin 目录下。

    第三步,用 javah -jni 命令,根据class字节码文件生成.h头文件(-jni 参数是可选的)

    javah -jni -classpath ./bin -d ./jni com.study.jnilearn.HelloWorld  

    默认生成的.h头文件名为:com_study_jnilearn_HelloWorld.h(包名+类名.h),也可以通过-o参数指定生成头文件名称:

    javah -jni -classpath ./bin -o HelloWorld.h com.study.jnilearn.HelloWorld  

    参数说明:

    • classpath:类搜索路径,这里表示从当前的 bin 目录下查找
    • d:将生成的头文件放到当前的 jni 目录下
    • o: 指定生成的头文件名称,默认以类全路径名生成(包名+类名.h)

    注意:-d-o只能使用其中一个参数。

    第四步,用本地代码实现.h头文件中的函数

    • com_study_jnilearn_HelloWorld.h
    /* DO NOT EDIT THIS FILE - it is machine generated */  
    #include <jni.h>  
    /* Header for class com_study_jnilearn_HelloWorld */  
    
    #ifndef _Included_com_study_jnilearn_HelloWorld  
    #define _Included_com_study_jnilearn_HelloWorld  
    #ifdef __cplusplus  
    extern "C" {  
    #endif  
    /* 
     * Class:     com_study_jnilearn_HelloWorld 
     * Method:    sayHello 
     * Signature: (Ljava/lang/String;)Ljava/lang/String; 
     */  
    JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello  
      (JNIEnv *, jclass, jstring);  
    
    #ifdef __cplusplus  
    }  
    #endif  
    #endif  
    • HelloWorld.c
    // HelloWorld.c  
    
    #include "com_study_jnilearn_HelloWorld.h"  
    
    #ifdef __cplusplus  
    extern "C"  
    {  
    #endif  
    
    /* 
     * Class:     com_study_jnilearn_HelloWorld 
     * Method:    sayHello 
     * Signature: (Ljava/lang/String;)Ljava/lang/String; 
     */  
    JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(  
            JNIEnv *env, jclass cls, jstring j_str)  
    {  
        const char *c_str = NULL;  
        char buff[128] = { 0 };  
        c_str = (*env)->GetStringUTFChars(env, j_str, NULL);  
        if (c_str == NULL)  
        {  
            printf("out of memory.
    ");  
            return NULL;  
        }  
        (*env)->ReleaseStringUTFChars(env, j_str, c_str);  
        printf("Java Str:%s
    ", c_str);  
        sprintf(buff, "hello %s", c_str);  
        return (*env)->NewStringUTF(env, buff);  
    }  
    
    #ifdef __cplusplus  
    }  
    #endif  

    第五步,将 C/C++ 代码编译成本地动态库文件动态库文件名命名规则:lib+动态库文件名+后缀(操作系统不一样,后缀名也不一样)如:

    • Mac OS X : libHelloWorld.jnilib
    • Windows :HelloWorld.dll(不需要 lib 前缀)
    • Linux/Unix:libHelloWorld.so

    1.Mac OS X

    gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin  

    $JAVA_HOME目录在:/Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home (可根据具体情况自己设置)

    参数选项说明:

    • -dynamiclib:表示编译成动态链接库
    • -o:指定动态链接库编译后生成的路径及文件名
    • -framework JavaVM -I:编译 JNI 需要用到 JVM 的头文件(jni.h),第一个目录是平台无关的,第二个目录是与操作系统平台相关的头文件

    2.Windows (以 Windows7 下 VS2012 为例)

    开始菜单-->所有程序-->Microsoft Visual Studio 2012-->打开 VS2012 X64 本机工具命令提示,用cl命令编译成dll动态库:

    cl -I"%JAVA_HOME%include" -I"%JAVA_HOME%includewin32" -LD HelloWorld.c -FeHelloWorld.dll   

    参数选项说明:

    • -I :和 mac os x 一样,包含编译 JNI 必要的头文件
    • -LD:标识将指定的文件编译成动态链接库
    • -Fe:指定编译后生成的动态链接库的路径及文件名

    3.Linux/Unix

    gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared HelloWorld.c -o libHelloWorld.so  

    参数说明:

    • -I: 包含编译JNI必要的头文件
    • -fPIC: 编译成与位置无关的独立代码
    • -shared:编译成动态库
    • -o: 指定编译后动态库生成的路径和文件名

    第六步,运行 Java 程序

    Java 在调用 native (本地)方法之前,需要先加载动态库。如果在未加载动态之前就调用 native 方法,会抛出找不到动态链接库文件的异常。如下所示:

    Exception in thread "main" java.lang.UnsatisfiedLinkError: com.study.jnilearn.HelloWorld.sayHello(Ljava/lang/String;)Ljava/lang/String;  
        at com.study.jnilearn.HelloWorld.sayHello(Native Method)  
        at com.study.jnilearn.HelloWorld.main(HelloWorld.java:9)  

    一般在类的静态(static)代码块中加载动态库最合适,因为在创建类的实例时,类会被 ClassLoader 先加载到虚拟机,随后立马调用类的 static 静态代码块。这时再去调用 native 方法就万无一失了。加载动态库的两种方式:

    System.loadLibrary("HelloWorld");  
    System.load("/Users/yangxin/Desktop/libHelloWorld.jnilib"); 

    方式1:只需要指定动态库的名字即可,不需要加lib前缀,也不要加.so.dll.jnilib后缀

    方式2:指定动态库的绝对路径名,需要加上前缀和后缀

    如果使用方式1,java 会去 java.library.path 系统属性指定的目录下查找动态库文件,如果没有找到会抛出java.lang.UnsatisfiedLinkError 异常。

    Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld2 in java.library.path  
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)  
        at java.lang.Runtime.loadLibrary0(Runtime.java:845)  
        at java.lang.System.loadLibrary(System.java:1084)  
        at com.study.jnilearn.HelloWorld.<clinit>(HelloWorld.java:13)  

    大家从异常中可以看出来,他是在 java.library.path 中查找该名称对应的动态库,如果在 Mac 下找libHelloWorld.jnilib 文件,linux 下找 libHelloWorld.so 文件,Windows 下找 libHelloWorld.dll 文件,可以通过调用 System.getProperties("java.library.path")方法获取查找的目录列表,下面是我本机mac os x 系统下的查找目录:

    String libraryDirs = System.getProperty("java.library.path");  
    System.out.println(libraryDirs);  
    // 输出结果如下:  
    /Users/yangxin/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:

    有两种方式可以让 Java 从 java.library.path 找到动态链接库文件,聪明的你应该已经想到了。

    方式1:将动态链接库拷贝到java.library.path目录下

    方式2:给 jvm 添加“-Djava.library.path=动态链接库搜索目录”参数,指定系统属性 java.library.path 的值 java -Djava.library.path=/Users/yangxin/Desktop Linux/Unix 环境下可以通过设置 LD_LIBRARY_PATH 环境变量,指定库的搜索目录。

    运行写好的 Java 程序了,结果如下:

    yangxin-MacBook-Pro:JNILearn yangxin$ java -classpath ./bin com.study.jnilearn.HelloWorld  
    Java Str:yangxin  
    hello yangxin  

    如果没有将动态库拷贝到本地库搜索目录下,执行java命令,可通过添加系统属性 java.library.path 来指定动态库的目录,如下所示:

    yangxin-MacBook-Pro:JNILearn yangxin$ java -Djava.library.path=/Users/yangxin/Desktop -classpath ./bin com.study.jnilearn.HelloWorld  
    Java Str:yangxin  
    hello yangxin  
     
  • 相关阅读:
    找回感觉的练习
    Java住房公积金管理系统---长春工业大学期末实训
    11.6_Filter
    11.5_Listener
    11.4_Ajax & Jquery
    11.3_MVC设计模式
    11.2_数据库连接池&DBUtils
    11.1_事物
    10.7_JSP & EL & JSTL
    10.6_Cookie&Session
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6079629.html
Copyright © 2020-2023  润新知