下调用句柄
调用原生函数
下调用句柄 是 CoddyKit 上的免费 Java Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
调用本地函数
下调用是指从 Java向下调用本地代码。FFM 会将每个本地函数表示为一个由 Linker 构建的 MethodHandle。
本课将逐步介绍如何查找、描述并调用 C 函数。
本地链接器
Linker.nativeLinker() 会返回运行平台对应的链接器。它了解 C 的调用约定,并能生成指向本地函数的句柄。
import java.lang.foreign.Linker;
public class Main {
public static void main(String[] args) {
Linker linker = Linker.nativeLinker();
System.out.println("Got native linker: " + linker);
}
}查找符号
您需要先获取函数的地址。linker.defaultLookup() 会搜索标准 C 库;SymbolLookup.libraryLookup(name, arena) 会加载指定的共享库。
import java.lang.foreign.*;
public class Main {
public static void main(String[] args) {
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MemorySegment addr = stdlib.find("strlen").orElseThrow();
System.out.println("strlen address found: " + addr);
}
}描述签名
FunctionDescriptor 使用布局来描述 C 函数签名。对于 size_t strlen(const char*),返回值为长整数,参数为指针(ADDRESS)。
import java.lang.foreign.*;
public class Main {
public static void main(String[] args) {
FunctionDescriptor desc = FunctionDescriptor.of(
ValueLayout.JAVA_LONG, // return: size_t
ValueLayout.ADDRESS); // arg: const char*
System.out.println("Descriptor: " + desc);
}
}构建句柄
将符号和描述符结合起来,通过 downcallHandle 获取一个 MethodHandle,然后就可以像调用任何 Java 方法一样调用它。
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class Main {
public static void main(String[] args) {
Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
linker.defaultLookup().find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
System.out.println("Handle ready: " + strlen.type());
}
}调用函数
要调用 strlen,需要传入指向本地字符串的指针。在 Arena 中分配字符串,然后对句柄执行 invoke。结果会以 Java 长整数值的形式返回。
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class Main {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
linker.defaultLookup().find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
try (Arena arena = Arena.ofConfined()) {
MemorySegment str = arena.allocateUtf8String("Hello, FFM");
long len = (long) strlen.invoke(str);
System.out.println("Length = " + len);
}
}
}映射类型
每种 C 类型都对应一种布局:
int对应ValueLayout.JAVA_INTlong/size_t对应JAVA_LONGdouble对应JAVA_DOUBLE- 任何指针对应
ADDRESS
必须正确完成这些映射,否则调用会损坏数据。
返回指针的函数
当 C 函数返回指针时,句柄会返回一个长度为零的 MemorySegment。要读取其起始位置之后的数据,必须使用 reinterpret(byteSize) 将其重新解释为正确的大小。
传递多个参数
FunctionDescriptor.of(returnLayout, arg1, arg2, ...) 按顺序列出参数。对于返回值为 void 的函数,请使用 FunctionDescriptor.ofVoid(args...),不要提供返回布局。
import java.lang.foreign.*;
public class Main {
public static void main(String[] args) {
// int (*)(const char*, int)
FunctionDescriptor two = FunctionDescriptor.of(
ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT);
// void (*)(int)
FunctionDescriptor noReturn = FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT);
System.out.println(two + " / " + noReturn);
}
}使用 throws 处理错误
由于 MethodHandle.invoke 声明了 throws Throwable,调用它的方法必须处理或声明该异常。这就是示例声明 throws Throwable 的原因。
缓存句柄
构建下调用句柄的开销相对较高,因此应只执行一次,然后重复使用。常见做法是在类初始化期间,将句柄绑定到 static final 字段中,之后多次调用它。
句柄本身支持线程安全的并发调用。
快速检查
回想一下,如何描述 C 指针参数。
总结
您已经学会了调用本地函数:
- 结合使用
Linker.nativeLinker()和SymbolLookup来查找符号 FunctionDescriptor使用布局描述签名downcallHandle会生成一个可以执行invoke的MethodHandle- 指针对应
ADDRESS;invoke会抛出Throwable
下一步:使用布局和结构体描述复杂的本地数据。
常见问题解答
「下调用句柄」课时是免费的吗?
是的 — 「下调用句柄」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「下调用句柄」这节课中我会学到什么?
调用原生函数 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「下调用句柄」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。