Java 本地接口面临的比较大的问题有两个。一个是 C 语言编译、链接带来的问题,因为 Java 本地接口实现的动态库是平台相关的,所以就没有了 Java 语言“一次编译,到处运行”的跨平台优势;另一个问题是,因为逃脱了 JVM 的语言安全机制,JNI 本质上是不安全的。
Java 的外部函数接口,是 Java 语言的设计者试图解决这些问题的一个探索。
面的代码,就是一个使用 Java 的外部函数接口实现的“Hello, world!"的小例子。
import java.lang.invoke.MethodType;
import jdk.incubator.foreign.*;
public class HelloWorld {
public static void main(String[] args) throws Throwable {
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
CLinker cLinker = CLinker.getInstance();
MemorySegment helloWorld =
CLinker.toCString("Hello, world!\n", scope);
MethodHandle cPrintf = cLinker.downcallHandle(
CLinker.systemLookup().lookup("printf").get(),
MethodType.methodType(int.class, MemoryAddress.class),
FunctionDescriptor.of(CLinker.C_INT, CLinker.C_POINTER));
cPrintf.invoke(helloWorld.address());
}
}
}
try-with-resource 语句里使用的 ResourceScope 这个类,定义了内存资源的生命周期管理机制。
CLinker,实现了 C 语言的应用程序二进制接口(Application Binary Interface,ABI)的调用规则。这个接口的对象,可以用来链接 C 语言实现的外部函数。
使用 CLinker 的函数标志符(Symbol)查询功能,查找 C 语言定义的函数 printf。在 C 语言里,printf 这个函数的定义就像下面的代码描述的样子:
int printf(const char *restrict format, ...);
这里使用了 JDK 7 引入的 MethodType,以及尚处于孵化期的 FunctionDescriptor。MethodType 定义了后面的 Java 代码必须遵守的调用规则。而 FunctionDescriptor 则描述了外部函数必须符合的规范。
对比使用 JNI 实现的代码,使用外部函数接口的代码,不再需要编写 C 代码。当然,也不再需要编译、链接生成 C 的动态库了。所以,由动态库带来的平台相关的问题,也就不存在了。
使用外部函数接口的代码,是 Java 代码,因此也受到 Java 安全机制的约束。
此文章为9月Day15学习笔记,内容来源于极客时间《深入剖析 Java 新特性》