对于java程序员来讲,java语言的好处和优势,我想不用我说了,你们天然会说出不少一套套的。但虽然咱们做为java程序员,但咱们不得不认可java语言也有一些它自己的缺点。好比在性能、和底层打交道方面都有它的缺点。因此java就提供了一些本地接口,他主要的做用就是提供一个标准的方式让java程序经过虚拟机与原生代码进行交互,这也就是咱们日常常说的java本地接口(JNI——java native Interface)。它使得在 Java 虚拟机(VM) 内部运行的Java 代码可以与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行互操做。JNI 最重要的好处是它没有对底层Java 虚拟机的实现施加任何限制。所以,Java虚拟机厂商能够在不影响虚拟机其它部分的状况下添加对JNI 的支持。程序员只需编写一种版本的本地应用程序或库,就可以与全部支持JNI 的Java 虚拟机协同工做。咱们来看一下为何要与原生代码进行交互:java
一:提升应用程序性能。咱们知道java对于c/c++、汇编语言来讲,显得比较“高级”。其实这里的高级就是简化了程序员的工做。不少底层的东西都让java虚拟机作了。但毕竟相对于直接访问底层来说,java多了一步虚拟机的过程,因此在性能上比着这些原生语言稍微有点慢。c++
二:实现一些与底层相关的功能。Java平台提供的标准类库,还有强大的API,虽然能完成大部分功能。但有些和底层硬件打交道的功能在java API提供的类库中仍是没法完成。程序员
三:与已有的使用原生代码编写的程序进行集成。在于操做系统上由c或者c++等原生语言编写的软件进行集成的时候,能够用JNI。编程
JNI 接口函数和指针windows
平台相关代码是经过调用 JNI 函数来访问Java 虚拟机功能的。JNI 函数可经过接口指针来得到。接口指针是指针的指针,它指向一个指针数组,而指针数组中的每一个元素又指向一个接口函数。每一个接口函数都处在数组的某个预约偏移量中。下图说明了接口指针的组织结构。数组
JNI 接口的组织相似于C++ 虚拟函数表或COM 接口。使用接口表而不使用硬性编入的函数表的好处是使JNI 名字空间与平台相关代码分开。虚拟机能够很容易地提供多个版本的JNI 函数表。例如,虚拟机可支持如下两个JNI 函数表:编程语言
1)一个表对非法参数进行全面检查,适用于调试程序;函数
2)另外一个表只进行 JNI 规范所要求的最小程度的检查,所以效率较高。性能
JNI 接口指针只在当前线程中有效。所以,本地方法不能将接口指针从一个线程传递到另外一个线程中。实现JNI 的虚拟机可将本地线程的数据分配和储存在JNI 接口指针所指向的区域中。编码
本地方法将JNI 接口指针看成参数来接受。虚拟机在从相同的 Java 线程中对本地方法进行屡次调用时,保证传递给该本地方法的接口指针是相同的。可是,一个本地方法可被不一样的Java 线程所调用,所以能够接受不一样的JNI 接口指针。
1)编写Java类代码
其中,须要JNI实现的方法应当用native关键字声明,在该类中,用System.loadLibrary()方法加载须要的动态连接库,关键代码以下:
//Compute.java public class Compute{ public native double sqrt(double params); static{ //调用动态连接库 System.loadLibrary("compute"); } }
2)编译成字节代码
在这个过程当中,因为采用了native关键字声明,Java编译器会忽视没有代码体的JNI方法部分。
3)生成相关JNI方法的头文件
这个过程的实现通常是经过利用jlavah-jni * class生成的(-jni能够省略),也能够手工生成该文件;可是因为 Java 虚拟机是根据必定的命名规范完成对JNI方法的调用,因此手工编写头文件须要特别当心。
上述文件产生的头文件部分代码以下:
//Compute.h extern"C"{ JNIEXPORT jdoubleJNICALL Java_Compute_comp(JNI-Env *, jobject, jdoubleArray);
JNI函数名称分为三部分:首先是Java关键字,供Java虚拟机识别;而后是调用者类名称(全限定的类名,其中用下划线代替名称分隔符);最后是对应的方法名称,各段名称之间用下划线分割。
JNI函数的参数也由三部分组成:首先是JNIEnv *,是一个指向JNI运行环境的指针;第二个参数随本地方法是静态仍是非静态而有所不一样一一非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用;其他的参数对应一般Java方法的参数,参数类型须要根据必定规则进行映射。
4)编写相应方法的实现代码
在编码过程当中,须要注意变量的长度问题,例如Java的整型变量长度为32位,而C语言为16位,因此要仔细核对变量类型映射表,防止在传值过程当中出现问题。
5)将JNI实现代码编译成动态连接库
编译过程是利用C/C++编译器实现的,在windows平台上,编译和链接的结果是动态连接库DLL文件。当要使用生成的动态连接库时,调用者类中须要显式调用该连接库dll文件。
通过上述处理,基本上完成了一个包含本地化方法的Java类的开发。
附录:将Jav类型映射到本地C 类型
为了使用方便,特提供如下定义。
#define JNI_FALSE 0
#define JNI_TRUE 1
jsize 整数类型用于描述主要指数和大小:
typedef jint jsize;
故障排除
当使用 JNI 从Java 程序访问本机代码时,您会遇到许多问题。您会遇到的三个最多见的错误是:
1)没法找到动态连接。它所产生的错误消息是:java.lang.UnsatisfiedLinkError。这一般指没法找到共享库,或者没法找到共享库内特定的本机方法。
2)没法找到共享库文件。当用 System.loadLibrary(String libname) 方法(参数是文件名)装入库文件时,请确保文件名拼写正确以及没有指定扩展名。还有,确保库文件的位置在类路径中,从而确保JVM 能够访问该库文件。
3)没法找到具备指定说明的方法。确保您的C/C++ 函数实现拥有与头文件中的函数说明相同的说明。
结束语
从 Java 调用C 或C++ 本机代码(虽然不简单)是Java 平台中一种良好集成的功能。虽然JNI 支持C 和C++,但C++ 接口更清晰一些而且一般比C 接口更可取。正如您已经看到的,调用C 或C++ 本机代码须要赋予函数特殊的名称,并建立共享库文件。当利用现有代码库时,更改代码一般是不可取的。要避免这一点,在C++ 中,一般建立代理代码或代理类,它们有专门的JNI 所需的命名函数。而后,这些函数能够调用底层库函数,这些库函数的说明和实现保持不变。