使用 JDK 的 Foreign Function & Memory API (FFM) 调用动态链接库示例
Foreign Function & Memory API (FFM) 是 Java 平台提供的一个用于与本地代码(非 Java 代码)和安全地访问堆外内存的 API。它是 Java 22 中的预览特性(JEP 454),旨在替代传统的 JNI(Java Native Interface),提供更高效、更安全的本地代码交互方式。
🔹1 FFM API 主要组成
FFM API 主要包含以下几个核心部分:
- 调用本地函数(Foreign Function)
- 允许 Java 代码直接调用 C 语言库中的函数,而无需 JNI 绑定。
- 使用
SymbolLookup
查找本地函数地址,结合Linker
进行调用。
- 管理本机内存(Memory Access)
- 允许 Java 代码分配、读取和写入堆外(Native)内存,而无需
Unsafe
。 - 使用
MemorySegment
进行安全的内存操作,避免内存泄漏和越界访问。
- 允许 Java 代码分配、读取和写入堆外(Native)内存,而无需
- 处理数据布局(Memory Layout)
- 通过
MemoryLayout
定义 C 结构体,使 Java 代码能安全访问本地数据结构。
- 通过
🔹 2 FFM API 的核心类
类 / 接口 | 作用 |
---|---|
MemorySegment | 表示一个可管理的本机内存区域,可用于读写数据。 |
MemoryLayout | 定义结构化数据布局,帮助解析 C 结构体。 |
MemorySession | 管理 MemorySegment 的生命周期,确保安全释放。 |
SymbolLookup | 用于查找本地库中的函数或变量地址。 |
Linker | 负责将 Java 方法和本地函数绑定,以进行调用。 |
🔹 3 示例:调用 C 语言的 函数
c语言代码
helloLibrary.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 打印 "Hello, World!"
void hello_world() {
printf("Hello, World!\n");
return;
}
// 返回动态分配的字符串 "Hello, World!"
char* create_string() {
char* str = (char*)malloc(15); // 分配内存以存储 "Hello, World!"
if (str) {
strcpy(str, "你好,世界!");
}
return str;
}
// 返回两个整数的和
int add_numbers(int a, int b) {
return a + b;
}
使用gcc
编译成动态链接库helloLibrary.dll
gcc:
gcc -shared -o helloLibrary.dll helloLibrary.c -static-libgcc -static-libstdc++
cl:
cl /LD helloLibrary.dll /o helloLibrary.dll
java 调用
FFMNative.java
package com.bingbaihanji;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
/**
* 使用 JDK 的 Foreign Function & Memory API (FFM) 调用动态链接库示例。
* <p>
* 此代码演示如何在新版本 JDK (>= 22) 中使用 `java.lang.foreign` API 进行外部库函数调用。
* <p>
* 步骤说明:
* 1. 获取 DLL 的路径。
* 2. 通过 `Linker` 创建本地库链接器。
* 3. 使用 `SymbolLookup` 查找动态链接库中的函数符号。
* 4. 通过 `downcallHandle` 创建方法句柄。
* 5. 通过 `invoke` 调用 DLL 中的 `hello_world`、`create_string` 和 `add_numbers` 函数。
*
* @author 冰白寒祭
* @date 2025-03-25 16:05:29
*/
public class FFMNative {
public static void main(String[] args) {
try {
// 获取动态链接库的路径
URL url = ResourceUtils.getURL("classpath:gcc/helloLibrary.dll");
if (!Objects.isNull(url)) {
Path linkLibraryPath = Paths.get(url.toURI());
// 创建本地库链接器
Linker linker = Linker.nativeLinker();
// 在全局 Arena(内存区域)内查找符号
SymbolLookup lookup = SymbolLookup.libraryLookup(linkLibraryPath, Arena.global());
// 1. 调用 hello_world 函数
MemorySegment helloSegment = lookup.find("hello_world")
.orElseThrow(() -> new RuntimeException("函数 hello_world not 未找到"));
MethodHandle helloHandle = linker.downcallHandle(helloSegment, FunctionDescriptor.ofVoid());
helloHandle.invoke();
// 2. 调用 create_string 函数,获取字符串
MemorySegment stringSegment = lookup.find("create_string")
.orElseThrow(() -> new RuntimeException("函数 create_string 未找到"));
MethodHandle stringHandle = linker.downcallHandle(
stringSegment,
FunctionDescriptor.of(ValueLayout.ADDRESS)
);
// 调用函数获取返回的指针
MemorySegment stringPtr = (MemorySegment) stringHandle.invoke();
// 将指针转换为实际的内存段(假设字符串最大长度为256)
MemorySegment stringMemory = stringPtr.reinterpret(256);
// 读取以null结尾的C字符串
String resultString = stringMemory.getString(0, StandardCharsets.UTF_8);
System.out.println("create_string 返回: " + resultString);
// 3. 调用 add_numbers 函数,计算两个整数之和
MemorySegment addSegment = lookup.find("add_numbers")
.orElseThrow(() -> new RuntimeException("函数 add_numbers 未找到"));
MethodHandle addHandle = linker.downcallHandle(addSegment, FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
int num1 = 10;
int num2 = 20;
int sum = (int) addHandle.invoke(num1, num2);
System.out.println("add_numbers(" + num1 + ", " + num2 + ") 返回: " + sum);
}
System.out.println("DLL 路径: " + url.toString());
} catch (Throwable e) {
throw new RuntimeException("调用 DLL 失败", e);
}
}
}
运行结果如下:
create_string 返回: 你好,世界!
add_numbers(10, 20) 返回: 30
DLL 路径: file:/D:/javaProject/bingbaihanji/jdk23-ffmapi/target/classes/gcc/hellolibrary.dll
Hello, World!
**4🔹 FFM API 相比 JNI 的优势
特性 | FFM API | JNI |
---|---|---|
性能 | 直接调用,无需中间代理层,更快 | 需要中间层转换,调用开销大 |
安全性 | 自动管理内存,减少泄漏风险 | 需要手动管理本机内存 |
易用性 | 纯 Java 代码,API 直观 | 需要 C 代码、javah 生成绑定 |
可维护性 | 代码更现代化,支持内存布局 | 代码复杂,难以调试 |
5🔹FFM API 的应用场景
- 调用 C 语言库(如 OpenGL、SQLite、libcurl)。
- 高性能数据处理(映射本机共享内存)。
- 系统编程(直接访问操作系统 API)。
- 替代 JNI,减少代码复杂度和维护成本。
### 总结
FFM API 通过 MemorySegment
管理本机内存,Linker
绑定本地函数,提供了比 JNI
更简单、安全、性能更好的方式来与原生代码交互