JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
JNI可以这样与本地程序进行交互:
1. 你可以使用JNI来实现“本地方法”(native methods),并在JAVA程序中调用它们。
2. JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。
JNI是JAVA平台的一个重要特征,使用它我们可以重用以前用C/C++写的大量代码。JNI的部分设计思想来源于Netscape的Java Runtime Interface(JRI)。使用JNI可以结合C++和Java两种语言的不同优势,使得我们的开发产品具有更强大的功能和更好的体验。
一方面,C++具有很好的执行效率和广泛的算法开源库,但是C++的开发成本和维护成本都过高,而另一方面,Java有更广的应用场景(Web开发、服务器开发、移动开发等),Java提供JNI接口,能够使C++代码运行在Java的环境中,使C++具有更好的应用场景,一方面能够将海量的C++开源库进行代码复用,减少了开发成本。
举个例子,我们实验室的项目中,要用到OpenCV库做视频处理和运算,实验室之前的开发都是Windows桌面端的,使用OpenCV/C++作为开发语言。但是现在要转移到Android APP开发中,如果采用OpenCV for Android进行开发,可能面临两个问题:
1. 之前做过的工作要重做,重复性建设;
2. OpenCV for Android对一些算法的支撑并没有提供很好的支撑,仅提供了C++的接口(简直是逼着你用JNI接口),如VideoCapture类中,受限于Android设备的特征,并无法直接调用摄像头和处理帧数据,只能在C++层面对视频进行解析,以及相关运算和操作。
如果使用JNI接口直接将原有的工作进行移植,那么一方面,可以代码复用,另一方面,也间接的解决了Java语言对算法支撑不足的问题,善莫大焉!
请记住,一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优点:
1. 程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
2. 程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。
我们用C++写一个A+B的函数,在VS中编译成dll动态链接库(Linux环境下是so库),然后在Java代码中加载该库,并传入A和B的值,在C++代码层计算并反回结果于Java层,完成一次数据传递。
1.先写Java代码,把需要的函数原型写出来,这里,我们定义C++做A+B的函数库叫做libmath,函数原型为 int addByCPP(int a,int b);
import java.util.Scanner;
import java.io.*;
public class Add{
public native static int addByCPP(int a,int b);
static{
System.loadLibrary("libmath");
}
public static void main(String args[]){
int a,b;
Scanner sc = new Scanner(System.in);
System.out.println("At java file,input a and b:");
a = sc.nextInt();
b = sc.nextInt();
int c = addByCPP(a,b);
System.out.println();
System.out.print("At java file:a+b=" + c);
}
}
其中,关键字native
定义了所声明的函数是JNI函数,System.loadLibrary
函数加载了其所在的动态链接库libmath
。
2.在cmd中使用javah命令生成对应的头文件
然后发现会多了一个Add.h文件
内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class Add */
#ifndef _Included_Add
#define _Included_Add
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Add
* Method: addByCPP
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_Add_addByCPP
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
这个文件不用动,我们需要做的是写出该函数的函数实现
1. 在VS2013中创建空项目,注意项目名字最好是libmath,对应上一步的库名。
2. 项目属性配置
在“项目属性” –> “VC++目录” –>“包含目录”中配置JNI文件的属性,路径是你的Java安装路径+\Java\jdk1.8.0_31\include
`
在“项目属性” –> “常规” –> “配置类型”中选 动态库.dll
写函数体
#include"Add.h"
#include
#include
using namespace std;
JNIEXPORT jint JNICALL Java_Add_addByCPP
(JNIEnv *, jclass, jint a, jint b){
cout << "At cpp file: a = " << a << " b = " << b << endl;
cout << "a+b = " << a + b;
return (a + b);
}
编译动态库,结果如下:
把dll库粘贴到Java代码的根目录下,开始运行
至此,第一个JNI程序,完成。
[1]. 百度百科,“JNI”词条
[2]. JNI Oracle官方参考文档
[3]. 翻译文档:JNI 编程指南