第一章 介绍
Java本地接口(JNI)是JAVA平台中的一个强大功能。使用JNI编程的程序能够调用C/C++编写的本地代码,同时也可以调用JAVA编写的代码。JNI允许程序员能够在使用JAVA带来的便利的同时,不必丢弃以前写的代码。由于JNI是JAVA平台的一部分,所以程序员能够一次性解决互操作性的问题,并期望他们的所有实现在JAVA平台都能够运行起来。
这本书是JNI的编程指导,同时也是JNI的参考手册,这本书包括三个部分:
- 第二章通过一个简单的例子来介绍JNI,它是一个为不熟悉JNI的初学者准备的教程
- 第二到第十章构成了一个程序员指南,大致给出一些JNI特性。我们会通过一系列短小但是有意义的例子去突出各种特性并介绍以证明在JNI编程中有用的技术
- 第十一到十三章介绍所有JNI类型和功能的最终规范。这些章节也可当做参考手册
这本书尝试吸引JNI不同需求的广泛受众。该教材和编程指南主要以初学程序员为主要受众,而经验丰富的开发者和JNI实现者会发现参考部分可能会更有用。该书的主要读者应该是使用JNI技术进行程序开发的开发者。本书中的“你”一次隐含指的是使用JNI进行编程的开发者,而不是指JNI的实现人员或者使用JNI编写的程序的最终使用人员。
本书假设你对于JAVA、C以及C++编程语言已经有了基本的认知。如果还没有,你可以参考下面其中一本优秀的参考书籍:The Java™ Programming Language, Second Edition, by Ken Arnold and James Gosling (Addison-Wesley, 1998), The C Programming Language, Second Edition, by Brian Kernighan and Dennis Ritchie (Prentice Hall, 1988), and The C++ Programming Language, Third Edition, by Bjarne Stroustrup (Addison-Wesley, 1997)。
1.1 Java平台以及主机环境
因为本书涉及到的程序是用JAVA编程语言和本地编程语言(C/C++)编写的,让我们首先弄清楚这些编程语言编程环境的范围。
JAVA平台是由JAVA虚拟机和JAVA应用程序编程接口组成的编程环境。JAVA应用程序是由Java语言编写的,并被编译成独立于极其的二进制类文件格式。任何一个虚拟机都可以执行这种类文件。Java应用程序编程接口有一系列已经定义好的类文件组成。Java平台的任何实现都必须保证支持Java编程语言、虚拟机和提供应用程序编程接口。
主机环境代表主机的操作系统、一系列本地库以及CPU指令集。本地应用程序是使用本地编程语言编写的,例如C和C++,然后编译成主机特定的二进制代码并与本地库链接。本地应用程序和本地库通常以来与特定的主机环境。例如:一个C应用程序只为一个操作系统而构建,通常是无法在其他的操作系统上运行的。
Java平台通常是部署在操作环境之上的。例如:Java运行环境(JRE)是Sun公司的产品,在现有的操作系统,例如Solaris和Windows上,它能够支持Java平台。Java平台提供了一组应用程序可以独立于主机环境的功能。
1.2 JNI的角色
当Java平台部署在主机环境之上时,可能希望或者需要允许Java应用程序与其他编程语言编写的本地代码紧密合作。程序员已经开始采用Java平台来构建传统上用C和C++编写的应用程序,由于现有遗留的代码,Java应用程序将和C/C++代码共存多年。
JNI是一个强大的功能,能够允许利用Java平台但是同时能够其他语言编写的代码。作为JAVA虚拟机的部分实现,JNI是一个双向接口,允许JAVA应用程序调用本地代码,反之亦然。图1.1表明了JNI的这种角色:
JNI旨在处理需要将Java应用程序和本地代码组合在一起的情况。作为一种双向接口,JNI能够支持两种类型的本地代码:本地库和本地应用程序。
- 你能够使用JNI去编写本地方法,然后允许Java应用程序调用使用本地块实现的方法。Java应用程序调用本地方法的方式和调用使用Java语言编写的方法的方式是一样的。然而在幕后,本地方法是使用另一种语言实现的,并驻留在本机库中。
- JNI支持一个调用接口,允许你将Java虚拟机实现嵌入到本机应用程序中。本地应用程序能够链接到一个实现了Java虚拟机的本地库中,然后是使用接口调用来执行Java编程语言编写的软件组件。例如,用C语言编写的浏览器能够运行已下载的潜入了Java虚拟机实现的applets程序。
1.3 使用JNI的意义
记住一旦程序使用了JNI,将失去了Java平台提供的两个优势。
首先,依赖于JNI的Java应用程序将不能再在多个主机环境中运行。尽管使用Java编程语言编写的部分代码是可以移植到做个主机环境当中的,但是有必要重新编译以本地语言编写的部分。
其次,尽管Java编程语言是类型安全且安全的,但是C/C++等本地语言却不是。因此在使用JNI编写应用程序的时候,需要格外的小心。行为不当的本地方法会引起整个应用程序崩溃。因此,在调用JNI方法前,Java应用程序将进行安全检查。
作为一般规则,你应该构建应用程序以使本地方法被定义在尽可能少的类中。这需要本地代码和应用程序的其余部分之间更清晰的隔离。
1.4 什么时候使用JNI
在开始使用JNI到项目之前,值得一步一步来调查是否有更好的替代方案。如上一节所述那样,与严格使用Java编程语言编写的应用程序相比,使用JNI的应用程序具有一些缺点。例如,你会失去Java编程语言提供的类型安全保障。
许多替代方法还允许Java应用程序与其他语言编写的代码进行互操作,例如:
- 一个Java应用程序可能通过TCP/IP连接或者通过其他进程间通信(IPC)机制与本地应用程序进行通信
- 一个Java应用程序可能通过JDBC API和传统的数据库进行连接
- Java应用程序可以利用分布式对象计数,如Java IDL API
这些替代方法的一个共同特征是Java应用程序和本地代码是驻留在不同的进程中(在某些场景中,是驻留在不同的机器上)。过程分离提供了重要的好处。进程提供的地址空间保护计数能够提供一个高度的故障隔离,崩溃的本地应用程序不会立即终止与之通过TCP/IP通信的Java应用程序。
然而有些时候你会发现,你可能会发现Java应用程序需要与驻留在同一进程中的本地代码进行通信。这时候,JNI计数就很有用了。考虑如下场景:
- Java API可能没有提供应用程序所需要的特定主机的相关功能。例如,一个应用程序可能想操作一个特定的文件,但是Java平台却没有提供相应的API支持,但是通过另一个进程来操作这个文件却是麻烦的和低效的。
- 你可能希望访问现有的本地库,而不愿意为在不同进行间复制和传输数据而有额外开销。在同一个进场中加载本地库将是一个更搞笑的方法
- 夸多进程的应用程序可能会导致不可接受的内存占用情况。如果这些继承驻留在同一个客户机上,这种情况往往是真的。将本地库加载到已经现有的应用程序中比启动新的程序,然后再将本地库加载到这个程序中需要更少的系统资源。
- 你可能希望通过较低级的语言(如汇编)来实现一小部分时间敏感的代码。如果一个3D密集型应用程序花费大部分时间在图形渲染中,你可能会发现有必要使用汇编代码来编写图形库的核心部分以达到最佳性能。
总而言之,如果一个Java应用程序必须和本地代码必须在同一个进程中,那么就使用JNI。
1.5 JNI的演变
自Java平台的早期阶段开始,Java应用程序与本机代码的互操作性得到肯定。Java平台的第一个版本的Java开发组件包含了一个本地方法接口,允许Java应用程序调用其他语言(例如C和C++)编写的函数。许多第三方应用程序以及Java类库(包括,例如java.lang,java.io和java.net)的实现依赖于本地方法接口访问底层主机中的功能环境。
但是很不幸,在第一版JDK中的本地方法接口存在两个主要的问题:
- 首先,本地代码访问对象中的字段作为C结构的成员。但是,Java虚拟机规范中没有定义对象在内存中是如何布置的。如果给定的Java虚拟机实现以不同于本地方法接口假定的方式布置对象,则必须重新编译本地方法库
- 其次,JDK版本1.0中的本地方法接口依赖于保守的垃圾收集器,因为本地方法可以获得直接指向虚拟机对象中的指针。任何使用更高级的垃圾收集算法的虚拟机实现都无法支持JDK 1.0版本中的本地方法接口。
JNI旨在克服这些问题。它是一种能够在各种主机环境中由所有Java虚拟机支持的接口。通过JNI:
- 每个虚拟机实现都可以支持更多的本地代码。
- 开发工具供应商不需要处理各种不同的本地方法接口
- 最重要的是,应用程序编程人员能够编写一个版本的本地代码,并且这个版本的代码将在不同的Java虚拟机实现上运行。
JNI第一次被支持是在JDK 1.1版本中。然而在其内部,JDK 1.1版本仍然使用旧类型的本地代码来完成Java编程接口。在Java SDK 1.2版本中不在是这样,本地方法已被重新,使其符合JNI的标准。
JNI是被所有的Java虚拟机实现所支持的本地接口。从JDK 1.1版本开始,你应该向JNI编程。虽然旧式本地编程接口在Java SDK 1.2版本上还有支持,但是在将来不会(或不能)在高级Java虚拟机实现中得到支持。
Java SDK 1.2版本中包含大量的JNI扩展功能。这些扩展功能是向后兼容的。JNI的未来发展将保持完整的二进制兼容性。
1.6 示例代码
本书包含大量演示JNI功能的示例程序。示例程序通常有多个由Java编程语言、C和C++编写的代码片段组成。有时候这些本地代是指Solaris和Win32主机中的特定功能。我们也演示如何使用JDK和Java SDK 2版本的命令行(如javah)构建JNI程序。
请记住,JNI的使用不限于特定的主机环境或特定的开发工具。本书着重于编写代码,而不是使用工具构建和运行代码。与JDK和Java SDK 2发行版捆绑的命令行工具相当原始。第三方功能可能会提供一种改进的方法来构建JNI的应用程序。我们鼓励你查阅和选择开发工具捆绑在一起的JNI相关文档,你可以从以下网站上下载本书中的示例代码以及本书的最新更新:http://java.sun.com/docs/books/jni/