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