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的环境变量

java 跨项目调用方法找不到文件路径 java项目相互调用_jni

java 跨项目调用方法找不到文件路径 java项目相互调用_java 跨项目调用方法找不到文件路径_02

然后在命令行中输入

gcc -v 

或者

g++ -v

 如果能出来如下内容,就代表环境配置完成了

java 跨项目调用方法找不到文件路径 java项目相互调用_java_03

    3.开发工具,各位随意(idea,eclipse都行),我就用Notepad++手撸好了

二、运行java

    1.创建项目,我这里创建一个文件夹,比如就叫JniTest,他就在我的桌面上

java 跨项目调用方法找不到文件路径 java项目相互调用_Java_04

    2.创建包名test,在包名下创建MainJava.java文件

java 跨项目调用方法找不到文件路径 java项目相互调用_jni_05

    3.使用Notepad++打开文件,并写一个main方法,先测试能运行起来

java 跨项目调用方法找不到文件路径 java项目相互调用_包名_06

javac MainJava.java

java 跨项目调用方法找不到文件路径 java项目相互调用_java 跨项目调用方法找不到文件路径_07

三、搭建jni

    1.在刚测试代码中加入native代码,如下图所示

public static native String jniTest();

java 跨项目调用方法找不到文件路径 java项目相互调用_java_08

javac MainJava.java 点击Enter,没有报错,然后退出到JniTest目录下(项目中就是包名外,与包名同级目录),运行javah -jni test.MainJava(包名.类名),完成后,可以看到包名同级目录下会生成一个test_MainJava.h的头文件

java 跨项目调用方法找不到文件路径 java项目相互调用_java_09

    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头文件同级目录存在,如下图所示

java 跨项目调用方法找不到文件路径 java项目相互调用_java_10

四、实战

1.在JniTest目录下创建xxxx.c文件,文件名随意,如下图所示

java 跨项目调用方法找不到文件路径 java项目相互调用_Java_11

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文件

    运行完成,如图所示:

java 跨项目调用方法找不到文件路径 java项目相互调用_Java_12

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

运行结果如图所示:

java 跨项目调用方法找不到文件路径 java项目相互调用_java 跨项目调用方法找不到文件路径_13

可以看到已经打印出来了hello from c

到此java调用c/c++就结束了