JNI是Java Native Interface的缩写,从Java1.1开始就成为了Java标准的一部分。JNI的定义了一种管理方式使Java能够与C/C++互相调用;它支持从动态链接库中loading code,且工作效率尚可。使用JNI调用现有的本地库,极大灵活了Java的开发(Java本身编写底层应用实现比较困难)。
JNI中的一些概念
-
native : Java语言中修饰本地方法的修饰符, 被该修饰符修饰的方法没有方法体;
-
Native方法 : 在Java语言中被native关键字修饰的方法是Native方法;
-
JNI层 : Java声明Native方法的部分;
-
JNI函数 : JNIEnv提供的函数, 这些函数在jni.h中进行定义;
-
JNI方法 : Native方法对应的JNI层实现的 C/C++方法, 即在jni目录中实现的那些C语言代码;
C与Java如何交流
-
JNI规范 : C语言与Java语言交流需要一个适配器, 中间件, 即 JNI, JNI提供了一种规范;
-
C语言中调用Java方法 : 可以让我们在C代码中找到Java代码class中的方法, 并且调用该方法;
-
Java语言中调用C语言方法 : 同时也可以在Java代码中, 将一个C语言的方法映射到Java的某个方法上;
-
JNI桥梁作用 : JNI提供了一个桥梁, 打通了C语言和Java语言之间的障碍;
JNI作用 :
-
扩展: JNI扩展了JVM能力, 驱动开发, 例如开发一个wifi驱动, 可以将手机设置为无限路由;
-
高效 : 本地代码效率高, 游戏渲染, 音频视频处理等方面使用JNI调用本地代码, C语言可以灵活操作内存;
-
复用 : 在文件压缩算法 7zip开源代码库, 机器视觉 openCV开放算法库 等方面可以复用C平台上的代码, 不必在开发一套完整的Java体系, 避免重复发明轮子;
-
特殊 : 产品的核心技术一般也采用JNI开发, 不易破解;
Java语言执行流程 :
-
编译字节码 : Java编译器编译 .java源文件, 获得.class 字节码文件;
-
装载类库 : 使用类装载器装载平台上的Java类库, 并进行字节码验证;
-
Java虚拟机 : 将字节码加入到JVM中, Java解释器 和 即时编译器 同时处理字节码文件, 将处理后的结果放入运行时系统;
-
调用JVM所在平台类库 : JVM处理字节码后, 转换成相应平台的操作, 调用本平台底层类库进行相关处理;
网上JNI的文章不多,我将自己的一些总结写在这里。如有错误,欢迎指出。
Java调用C/C++总结起来有如下三个步骤:
-
编写带有native方法的Java类,使用javac工具编译Java类
-
使用javah工具来生成与native方法对应的头文件(一般为XX.h文件)
-
实现这个头文件,并将其编译为动态链接库(.dll或.so)
下面是一个简单完整的Java调用C/C++的例子,改自IBM的一篇介绍JNI的文章和icejoywoo的博客。
编写Java类
我们来编写一个JNITest的Java类。
上面代码中的4个native方法就是我们需要用C来实现的方法。
编译JNITest文件,可以看到JNITest.class文件。
javah工具生成头文件
命令行运行
javah JNITest
可以看到目录下自动生成了一个头文件JNITest.h
可以看到有4个函数声明,命名方式为 Java_完整类名_方法名,完整类名包括包名,假设我的demo.JNITest是完整类名,对应的就是demo_JNITest(本文不是)。 注释中有个方法签名(Signature),通过一个表格来介绍Signature。
Java类型 | Signature | 备注 |
---|---|---|
boolean | Z | |
byte | B | |
char | C | |
short | S | |
int | I | |
long | L | |
float | F | |
double | D | |
void | V | |
object | L用/分割的完整类名 | 例如:Ljava/lang/String表示String类型 |
Array | [签名 | 例如:[I 表示int数组,[Ljava/lang/String表示String数组 |
Method | (参数签名) 返回类型签名 | 例如:([I)I 表示参数类型为int数组,返回int类型的方法 |
上面头文件的第一个函数声明
JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint);
注释中的签名是 Signature: (I)I
实现头文件中的函数
可以使用C/C++语言实现,下面是使用C语言实现的版本(其实C/C++)实现起来差不多。
C++的代码和C的很像,只有一个不同点
C代码: (*env)->GetStringUTFChars(env, string, 0);
C++代码: env->GetStringUTFChars(string, 0);
C语言中使用的是结构体的函数指针, 而在C++中使用的还是struct, 我们知道struct在C++中和class的功能是几乎一样的, struct也可以用来定义类, 所以env在C++中是个类对象的指针.
编译和运行
MS的编译器,编译C语言版本的.dll
用VS新建dll工程,编译即可。
Linux的gcc编译器,编译C语言版本的.so
gcc命令编译
gcc -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -I /home/mike_ming/workspace/JNI/ -fPIC -shared -o libJNITest.so JNIC.c
注意:
- -I /home/mike_ming/workspace/JNI/ 这个是指当前有JNITest.h文件的目录
- Linux规定库文件(.so)必须以lib开头,但是我们使用loadlibrary方法加载的是JNITest
运行
java -Djava.library.path=’.’ JNITest
-Djava.library.path 代表的就是当前的.so路径了
** 引用 **