JNI之简单介绍以及流程

参考网址:
参考此篇文章
参考JAVA核心卷技术II

java调用c程序

1. 问答形式

  1. 为什么需要调用c?

    因为java的内存管理使用垃圾管理机制,有的时候的确存在内存不能很快释放

    因为当前有很多的开源代码,源代码都是c/c++编写,比如音频相关的ffmepg,大多数语音引擎的代码都是c

  2. java使用什么方式调用c?

    java提供了用于和c交互的API,称为JNI

  3. java不能调用c++吗?

    java编程中可以调用c++,但是这不是JNI的优化,是c本身可以调用c++,看到这里就知道如果使用了c++来写程序,在要给JNI调用的时候就要加上extern c, 注意我们建议使用c++编程,因为类型检查更严格,RAII管理内存泄漏等

2. 开始

​ 编写一个简单的JNI调用:

​ 新建Hello.java

​ 1.在java方法中声明使用的native方法

package com.zero_waring.jni_test;
public class Hello {
    public native void getString();
}

​ 看起来像是一个函数声明,native提醒编译器该方法在外部调用,本地方法不包含任何java代码,后面加上分号结尾

  1. 生成对应的JNI的头文件

    首先对java编译下:

    javac com/zero_waring/jni_test/Hello.java

    然后到com目录下生成对应JNI头文件

    javah -classpath . -d ./com/zero_waring/jni_test com.zero_waring.jni_test.Hello

    注意:

    1. -classpath指代加载class的路径,因为执行程序Hello已经指明了包路径,所以classpath指代当前就可以
    2. -d 指明生成的.h的路径

    就会在com/zero_waring/jni_test目录下生成com_zero_waring_jni_test_Hello.h 这个是自动生成的文件

    注意一下名称是package + SourceName

    看一下内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_zero_waring_jni_test_Hello */

#ifndef _Included_com_zero_waring_jni_test_Hello
#define _Included_com_zero_waring_jni_test_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_zero_waring_jni_test_Hello
 * Method:    getString
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_zero_1waring_jni_1test_Hello_getString
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

学会c语言的对上面很熟悉,就是一个头文件,extern c 为了保证编译的是c风格的函数,告诉c++编译器使用c风格编译函数功能

JNICALL代表JNI调用的方法,Java_Hello_getString这个是固定的Java_{package_and_classname}_{function_name}(JNI_arguments)

参数里面 JNIEnv代表引用JNI的环境,通过这个指针我们可以调用一些JNI的方法 jobject引用了当前java的this 对象

但是细心的朋友们可以看出多了一个1,为啥呢?因为上面我们的包名有_字符

​ 如果你不想函数名中有1你就不要让函数名中用'',因为jni吧C函数名映射成java方法名的时候是com_mypackage_myclass_mymethod():com.mypackage.myclass.mymethod();也就是说jni是依靠''来间隔包、类、方法的,但是如果你的方法名中有''字符的话,jni必须能够区分方法名中的''字符还是间隔符,所以用加一个1后缀的方式来区分。

  1. 编写对应JNI头文件的c文件

    #include <jni.h>       // JNI header provided by JDK
    #include <iostream>    // C++ standard IO header
    #include "com_zero_waring_jni_test_Hello.h"  // Generated
    using namespace std;
    
    JNIEXPORT void JNICALL Java_com_zero_1waring_jni_1test_Hello_getString
      (JNIEnv *, jobject) {
        cout << "Hello World from C++!" << endl;
        return;
    }
    
  2. 编译c文件为动态库,在当前目录进行编译生成动态库

    g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -I ./ -shared com_zero_waring_jni_test_Hello.c -o libhello.so
    

    ​ -I 代表 头文件位置

    ​ -fPIC是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性

    ​ -shared是链接选项,告诉gcc生成动态库而不是可执行文件

    ​ 可以看到当前文件下生成了libhello.so

    ​ 库已经生成,下面就是在java中加载

  3. 加载c 动态库

    新建Main.java,在和Hello.java同级目录

    写入加载动态库的代码

    为了确保第一次使用类之前就会装载这个库,需要使用静态初始化代码块,System.loadLibrary("hello");就是加载libhello.so库

    package com.zero_waring.jni_test;
    public class Main {
    static {
        System.loadLibrary("hello");	
    }
    public static void main(String[] args) {
        new Hello().getString();
    }
    }
    

6.编译运行

因为上面生成了动态库,这里我们手动添加一下动态库的搜索路径
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
javac Main.java Hello.java
//因为Main.java依赖动态库,所以要一起编译Hello.java然后生成了Hello.java
java -classpath ~/test com.zero_waring.jni_test.Main

然后就看见打印了Hello World from C++!

总结

用一张图归纳一下这整个流程

posted @ 2020-11-22 22:53  make_wheels  阅读(325)  评论(0)    收藏  举报