第一章:介绍
本章介绍了Java本地接口(JNI)。JNI是一个本地编程接口。它允许在Java虚拟机(VM)内部运行的Java代码与用其他编程语言(如C、C++和汇编)编写的应用程序和库进行交互。
JNI最重要的好处是它不对底层Java VM的实现施加任何限制。因此,Java VM供应商可以在不影响VM其他部分的情况下添加对JNI的支持。程序员可以编写一个本地应用程序或库的版本,并期望它能在支持JNI的所有Java VM上运行。
本章涵盖以下主题:
Java本地接口概述
虽然您可以完全使用Java编写应用程序,但在某些情况下,Java本身无法满足应用程序的需求。程序员使用JNI编写Java本地方法来处理那些无法完全使用Java编写的应用程序的情况。
以下示例说明何时需要使用Java本地方法:
- 标准Java类库不支持应用程序所需的平台相关功能。
- 您已经有一个用另一种语言编写的库,并希望通过JNI使其可供Java代码访问。
- 您想要在较低级别语言(如汇编)中实现一小部分时间关键代码。
通过使用JNI编程,您可以使用本地方法来:
- 创建、检查和更新Java对象(包括数组和字符串)。
- 调用Java方法。
- 捕获和抛出异常。
- 加载类并获取类信息。
- 执行运行时类型检查。
您还可以使用JNI与调用API一起使用,以使任意本地应用程序嵌入Java VM。这使程序员可以轻松地使其现有应用程序支持Java,而无需链接VM源代码。
历史背景
来自不同供应商的VM提供不同的本地方法接口。这些不同的接口迫使程序员在给定平台上生成、维护和分发多个版本的本地方法库。
我们简要研究了一些本地方法接口,例如:
- JDK 1.0本地方法接口
- Netscape的Java运行时接口
- 微软的原生本地接口和Java/COM接口
JDK 1.0本地方法接口
JDK 1.0随附了一个本地方法接口。不幸的是,这个接口不适合其他Java VM采用的主要原因有两个。
首先,本地代码将Java对象的字段作为C结构的成员进行访问。然而,《Java语言规范》没有定义对象在内存中的布局方式。如果Java VM以不同的方式在内存中布局对象,那么程序员将不得不重新编译本地方法库。
其次,JDK 1.0的本地方法接口依赖于保守的垃圾收集器。例如,对unhand
宏的不受限制的使用使得需要保守地扫描本地堆栈。
Java运行时接口
Netscape提出了Java运行时接口(JRI),这是一个为Java虚拟机提供的服务的通用接口。JRI设计时考虑了可移植性---它对底层Java VM的实现细节做出了少量假设。JRI解决了各种问题,包括本地方法、调试、反射、嵌入(调用)等。
原生本地接口和Java/COM接口
微软的Java VM支持两种本地方法接口。在低级别上,它提供了一个高效的原生本地接口(RNI)。RNI提供了与JDK的本地方法接口高度的源级向后兼容性,尽管有一个主要区别。与依赖保守垃圾收集不同,本地代码必须使用RNI函数与垃圾收集器显式交互。
在较高级别上,微软的Java/COM接口为Java VM提供了一个与语言无关的标准二进制接口。Java代码可以像操作Java对象一样使用COM对象。Java类也可以作为COM类暴露给系统的其他部分。
目标
我们相信一个统一、经过深思熟虑的标准接口对每个人都有以下好处:
- 每个VM供应商可以支持更多的本地代码。
- 工具构建者不必维护不同类型的本地方法接口。
- 应用程序员将能够编写一个版本的本地代码,并且该版本将在不同的VM上运行。
实现标准本地方法接口的最佳方法是让所有对Java VM感兴趣的各方参与其中。因此,我们组织了一系列关于统一本地方法接口设计的讨论。从讨论中清楚地看出,标准本地方法接口必须满足以下要求:
- 二进制兼容性 - 主要目标是在给定平台上实现本地方法库的二进制兼容性,使程序员只需为给定平台维护一个版本的本地方法库。
- 效率 - 为了支持时间关键代码,本地方法接口必须几乎不带来额外开销。确保VM独立性(从而实现二进制兼容性)的所有已知技术都会带来一定的开销。我们必须在效率和VM独立性之间做出妥协。
- 功能性 - 接口必须暴露足够的Java VM内部信息,以允许本地方法执行有用的任务。
Java本地接口方法
我们希望采用现有方法之一作为标准接口,因为这将对必须学习不同VM中多个接口的程序员施加最小的负担。不幸的是,目前没有现有解决方案完全满足我们的目标。
Netscape的JRI是我们设想的便携式本地方法接口最接近的,也是我们设计的起点。熟悉JRI的读者会注意到API命名约定、方法和字段ID的使用、本地和全局引用的使用等方面的相似之处。然而,尽管我们尽最大努力,JNI与JRI不具有二进制兼容性,尽管VM可以同时支持JRI和JNI。
微软的RNI相对于JDK 1.0是一个改进,因为它解决了本地方法与非保守垃圾收集器一起工作的问题。然而,RNI不适合作为独立于VM的本地方法接口。与JDK一样,RNI本地方法将Java对象作为C结构访问,导致两个问题:
- RNI暴露了内部Java对象的布局给本地代码。
- 直接将Java对象作为C结构访问使得无法有效地包含“写屏障”,这在高级垃圾收集算法中是必要的。
作为一个二进制标准,COM确保在不同VM之间完全的二进制兼容性。调用COM方法只需要间接调用,带来很少的开销。此外,COM对象在解决版本问题方面比动态链接库有了很大的改进。
将COM作为标准Java本地方法接口的使用受到一些因素的阻碍:
- 首先,Java/COM接口缺少某些所需功能,例如访问私有字段和引发一般异常。
- 其次,Java/COM接口自动为Java对象提供标准的IUnknown和IDispatch COM接口,以便本地代码可以访问公共方法和字段。不幸的是,IDispatch接口无法处理重载的Java方法,并且在匹配方法名称时不区分大小写。此外,通过IDispatch接口公开的所有Java方法都被包装以执行动态类型检查和强制转换。这是因为IDispatch接口是为弱类型语言(如Basic)设计的。
- 第三,与处理单个低级功能不同,COM旨在允许软件组件(包括完整的应用程序)共同工作。我们认为将所有Java类或低级本地方法视为软件组件是不合适的。
- 第四,COM在UNIX平台上的支持不足,这阻碍了对COM的立即采用。
尽管Java对象没有作为COM对象暴露给本地代码,但JNI接口本身与COM具有二进制兼容性。JNI使用与COM相同的跳转表结构和调用约定。这意味着,一旦支持跨平台的COM,JNI可以成为Java VM的COM接口。
认为JNI不是给定Java VM支持的唯一本地方法接口。标准接口使程序员受益,他们希望将其本地代码库加载到不同的Java VM中。在某些情况下,程序员可能必须使用较低级别的、特定于VM的接口来实现最高效率。在其他情况下,程序员可能会使用更高级别的接口来构建软件组件。事实上,随着Java环境和组件软件技术变得更加成熟,本地方法将逐渐失去其重要性。
编程到JNI
本地方法程序员应该编程到JNI。通过遵循JNI标准,您可以使本地库在给定Java VM中运行的机会最大化,而不受最终用户可能运行的供应商VM的未知因素的影响。
如果您正在实现Java VM,您应该实现JNI。JNI经过时间考验,确保不会对您的VM实现施加任何开销或限制,包括对象表示、垃圾收集方案等。如果您遇到我们可能忽视的任何问题,请发送反馈给我们。