JNI是Java Native Interface的缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的。当然也可以与其他语言交互,只要调用约定受支持就可以了。
但是这样做丧失了平台可移植性,但是有些时候我们这样做比较方便,比如以前我们项目组做网管软件,需要很多获取网络中的硬件信息,当然底层使用c语言进行信息扫描,一开始我们采用socket通信,实现c与java通信;但是随着数据信息的增加,socket通信任务就繁重起来,因此我们不得不自定义协议来实现socket下的复杂类型的数据通信。最后我们采用JNI非常方便的就完成了c与java的通信。主要是java调用c语言的方法。
这几天有学生问关于JNI的问题,那么就以实例 做一些简单介绍吧。
先说一下JNI开发的步骤:
- 编写带有native声明的方法的java类
- 使用javac命令编译所编写的java类
- 使用javah java类名 生成扩展名为h的头文件
- 使用C/C++实现本地方法
- 将C/C++编写的文件生成动态连接库
- 调用本地方法OK
1.java类:HelloTest.java
public class HelloTest {
public HelloTest() {
}
static {
try {
System.loadLibrary("hello");
} catch (Exception ex) {
ex.printStackTrace();
}
}
//求平方
private native int square(int i);
public static void main(String[] args)
{
HelloTest ht=new HelloTest();
int result=ht.square(3);
//下面输出应该是9
System.out.println("the result is :" + result);
}
}
这一步应该都没有问题,但是需要解释两个地方:
1).声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明改方法为native的,并且不能实现。该方法由其他语言进行实现。一般是用c语言实现。那么就需要进行java的数据类型与c语言的数据类型映射的问题,比如:private native int square(int i);参数与返回值的数据类型都是int,那么c语言实现该方法时对应的数据类型是什么呢?String,float等等又该如何进行映射呢?
java类型 | c语言类型 | 说明 |
boolean | jboolean | 无符号 8位 |
byte | jbyte | 无符号 8位 |
char | jchar | 无符号,16 位 |
short | jshort | 有符号,16 位 |
int | jint | 有符号,32 位 |
long | jlong | 有符号,64 位 |
float | jfloat | 32位 |
double | jdouble | 64位 |
void | void |
还要引用数据类型:
jobject
|--jclass
|--jstring
|--jarray
|--jobjectArray
|--jbooleanArray
|--jbyteArray
|--jcharArray
|--jshortArray
|--jintArray
|--jlongArray
|--jfloatArray
|--jdoubleArray
当然,还有其他的数据类型没有列举。
2).System.loadLibrary("hello"):就是加载名称是hello的动态链接库,如果是windows下则为dll文件,如果是linux下则是so文件,系统根据jdk自动判断平台。一般该文件放在系统环境变量path中,因此我们可以吧动态链接库文件放在%JAVA_HOME%/bin目录下即可。jdk中该方法相当于:
Runtime.getRuntime().loadLibrary(String);
当然与该方法相似的是System.load(String fileName) 和Runtime.getRuntime().load(String fileName)。程序本地方法调用前应该先加载native方法的本地实现(就是实现本地方法的动态链接库),因此该方法一般放在静态块中。
2.编译
执行javac HelloTest.java 如果没有报错则编译成为HelloTest.class文件,这一步没有什么问题。
3.生成头文件
执行javah HelloTest 会生成一个文件HelloTest.h文件,内容大致如下:
#include <jni.h>
#ifndef _Included_HelloTest
#define _Included_HelloTest
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_HelloTest_square
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
关键是
JNIEXPORT jint JNICALL Java_HelloTest_square (JNIEnv *, jobject, jint);
该方法是从java类生成供c语言实现的方法,该文件尽量不要修改。这里的数据类型就是上面的提到的映射后的数据类型,该数据类型是在
%JAVA_HOME%\include\jni.h中定义,
4.实现该方法。hello.c
#include "HelloTest.h"
JNIEXPORT jint JNICALL Java_HelloTest_square(JNIEnv *env, jobject obj, jint r)
{
return r*r;
}
这一步也应该没有什么问题。接下来就是要生成动态连接库了。
5.生成动态链接库文件.
生成动态连接库文件,就是编译该c文件输出为动态连接库,如果是windows则生成dl文件如果是linux则生成so文件。不同的平台c语言的编译器各不相同,现在我在windows平台下使用vc的编译器进行编译(只要安装了vc++6.0就可以了)关于vc编译器CL的用法,大家可以google一下,非常麻烦的。
cl -I%java_home%\include -I%java_home%\include\win32 -LD hello.c -Fehello.dll
生成的dll文件名在选项-Fe后面配置,这里是hello,因为在HelloTest.java文件中我们loadLibary的时候使用的名字是hello。两个地方要相同。另外需要将-I%java_home%\include -I%java_home%\include\win32参数加上。
生成hello.dll文件之后拷贝hello.dll到%java_home%\bin目录下即可使用,最好吧HelloTest.h文件拷贝到%java_home%\include目录下。
6.调用该方法执行main函数即可运行成功:
java HelloTest 命令运行测试控制台输出结果是9
ok运行成功。