JNI之简单介绍以及流程
参考网址:
参考此篇文章
参考JAVA核心卷技术II
java调用c程序
1. 问答形式
-
为什么需要调用c?
因为java的内存管理使用垃圾管理机制,有的时候的确存在内存不能很快释放
因为当前有很多的开源代码,源代码都是c/c++编写,比如音频相关的ffmepg,大多数语音引擎的代码都是c
-
java使用什么方式调用c?
java提供了用于和c交互的API,称为JNI
-
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代码,后面加上分号结尾
-
生成对应的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
注意:
- -classpath指代加载class的路径,因为执行程序Hello已经指明了包路径,所以classpath指代当前就可以
- -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后缀的方式来区分。
-
编写对应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; } -
编译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中加载
-
加载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++!
总结
用一张图归纳一下这整个流程


浙公网安备 33010602011771号