JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 [1] 从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
以上是百度对JNI的解释,其实说白了就一句话:jni是用来打通java和c\c++通信的,如此而已.下面介绍一下如何实现在java中运行jni,调用c\c++
这里我使用的windwos开发环境,其他系统环境类似,就不一一介绍了
一、环境搭建
1.jdk环境搭建(这个就不说了)
2.安装MinGW,官网可能下载较慢,这里我提供下载地址 提取码:nbc3
配置下MinGw的环境变量
然后在命令行中输入
gcc -v
或者
g++ -v
如果能出来如下内容,就代表环境配置完成了
3.开发工具,各位随意(idea,eclipse都行),我就用Notepad++手撸好了
二、运行java
1.创建项目,我这里创建一个文件夹,比如就叫JniTest,他就在我的桌面上
2.创建包名test,在包名下创建MainJava.java文件
3.使用Notepad++打开文件,并写一个main方法,先测试能运行起来
javac MainJava.java
三、搭建jni
1.在刚测试代码中加入native代码,如下图所示
public static native String jniTest();
javac MainJava.java 点击Enter,没有报错,然后退出到JniTest目录下(项目中就是包名外,与包名同级目录),运行javah -jni test.MainJava(包名.类名),完成后,可以看到包名同级目录下会生成一个test_MainJava.h的头文件
3.打开头文件,可以看到如下代码
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_MainJava */
#ifndef _Included_test_MainJava
#define _Included_test_MainJava
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: test_MainJava
* Method: jniTest
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_test_MainJava_jniTest
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
下面,详解一下这段代码,无兴趣的可以直接跳过这一段,直接进入第4步:
../jdk路径/include目录下,目录下的这个jni.h待会要拷贝到项目中来使用,否则会找不到jni文件
>2.其次可以看到
#ifndef _Included_test_MainJava
#define _Included_test_MainJava
#ifdef __cplusplus
#endif
这些代码,这其实是为了防止库文件重复引入,影响编译效率,什么意思? 比如说A文件引入了Z库,B文件也引入了Z库,加入了if判断后,如果库已经被引入,则不再重复引入.不加if判断针对运行期间没有影响,主要是编译效率会下降
>3.还可以看到
extern "C"
这种代码,这种代码实质上是为了兼容C所做的处理,如果项目中没有用到C代码,可以去除,否则如果涉及到C代码会报错,所以还是加上比较稳妥
>4.接着也是最主要的 JNIEXPORT jstring JNICALL Java_test_MainJava_jniTest (JNIEnv *, jclass);
JNIEXPORT:
翻看jni源码可以发现,这个宏实质上定义了一个__attribute__ ((visibility ("default"))),可以把它理解为java中的 public private这样的修饰,default就代表函数是可见的,如果是__attribute__ ((visibility ("hidden")))就是不可见的
jstring"
对应的是java中native方法的返回值
JNICALL
翻看源码发现里面是一个空定义的宏,没有实际意义,可以去除
Java_test_MainJava_jniTest
MainJava.jniTest,则在jni中表现为:com_2ma_ym1_MainJava_jniTest
(JNIEnv *, jclass)
JNIEnv* 可以理解为jni指针,它里面包含了jni中很多可供调用的api,以实现java和c/c++之间的转换和调用
jclass 参数传入,代表类;如果是非静态方法,则以jobject参数传入,代表当前对象
4.以上代码了解后,开始引入jni库,库的路径在../jdk路径/include目录下,将目录下的jni.h和../jdk路径/include/win32目录下的jni_md.h拷贝到项目中,与上面生成的test_MainJava.h头文件同级目录存在,如下图所示
四、实战
1.在JniTest目录下创建xxxx.c文件,文件名随意,如下图所示
2.打开xxxx.c文件,写入如下测试代码
#include "test_MainJava.h"
JNIEXPORT jstring JNICALL Java_test_MainJava_jniTest
(JNIEnv *env, jobject obj){
char* str = "hello from c";
return (*env)->NewStringUTF(env, str);
}
解释一下:
#include "test_MainJava.h"
引入项目头文件,这里解释一下""引入头文件和<>引入头文件的区别,""代表优先查找项目下的头文件,而<>会优先查找系统头文件
3.在命令行中输入
gcc -shared -o nnnn.dll xxxx.c 点击Enter运行,因为我是编写的c文件,所以我用gcc命令,如果是用c++写的就用g++,当然g++也是兼容c的
shared 代表生成的是动态库, 如果替换为static,则生成静态库, 静态库相当于组件化,在程序运行时就必须存在; 动态库相当于组件化,要使用的时候再加载
nnnn.dll是要生成的库文件名字
xxxx.c是刚写好的c文件
运行完成,如图所示:
4.在java代码中引入库文件
package test;
public class MainJava{
static{
System.load("C:/Users/Liera/Desktop/JniTest/nnnn.dll");
}
public static void main(String[] args){
//System.out.println("Hello Test");
System.out.println(jniTest());
}
public static native String jniTest();
}
5.再次编译并运行java代码
注意: 直接在JniTest目录下使用命令行,不要进入test目录下去编译,本次编译不像上面那样,上面只是为了测试能否生成class文件
javac -d . test/MainJava.java
java test.MainJava
运行结果如图所示:
可以看到已经打印出来了hello from c
到此java调用c/c++就结束了