0Pricing
Java Academy · 课时

下调用句柄

调用原生函数

下调用句柄 是 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_INT
  • long/size_t 对应 JAVA_LONG
  • double 对应 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为什么选择 FFM 而不是 JNI
  2. MemorySegment 与 Arena
  3. 下调用句柄
  4. 布局与结构体
← 返回 Java Academy