如何通过JEP 412外部函数与内存API在Java 17中调用C函数
Java 17 调用C函数极简完整示例(基于JEP 412 Foreign Function & Memory API,无第三方工具)
前置条件
- 已安装JDK 17及以上版本,配置好JAVA_HOME环境变量
- 已安装C编译工具链(GCC/Clang/VS Build Tools均可)
- 注意:Java 17中FFM API是孵化器特性,运行时需加额外参数
第一步:创建并编译C动态库
1.1 编写C代码
新建mymath.c文件,内容如下:
// 简单的加法函数,供Java调用 int add(int a, int b) { return a + b; }
1.2 编译为动态链接库
根据操作系统执行对应编译命令:
- Linux/macOS(生成
libmymath.so/libmymath.dylib):# Linux gcc -shared -fPIC -o libmymath.so mymath.c # macOS gcc -shared -fPIC -o libmymath.dylib mymath.c - Windows(生成
mymath.dll):cl /LD mymath.c /Fe:mymath.dll
编译完成后把生成的动态库文件放到Java项目的根目录即可。
第二步:编写Java调用代码
2.1 核心流程说明
全程只使用JDK内置API,无需任何额外工具:
- 获取系统原生链接器
- 加载编译好的C动态库
- 查找目标C函数的符号
- 定义函数的入参、出参类型描述符,生成MethodHandle
- 调用MethodHandle执行C函数
2.2 完整Java代码
新建FFMDemo.java:
import jdk.incubator.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; public class FFMDemo { public static void main(String[] args) throws Throwable { // 1. 获取系统原生链接器 Linker linker = Linker.nativeLinker(); // 2. 加载动态库,获取符号查找器 SymbolLookup lookup = SymbolLookup.loaderLookup(); // 3. 查找add函数的符号 MemorySegment addFunc = lookup.lookup("add") .orElseThrow(() -> new RuntimeException("找不到add函数,请检查动态库是否正确放置")); // 4. 构造函数描述符:返回值int,两个入参都是int,生成MethodHandle MethodHandle addHandle = linker.downcallHandle( addFunc, MethodType.methodType(int.class, int.class, int.class), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT) ); // 5. 调用C函数 int result = (int) addHandle.invokeExact(10, 20); System.out.println("C函数add(10,20)的返回结果:" + result); } }
第三步:编译运行Java代码
因为FFM API在Java17中是孵化器特性,编译和运行都需要加额外参数:
3.1 编译代码
javac --add-modules jdk.incubator.foreign FFMDemo.java
3.2 运行代码
- Linux/macOS:
java --add-modules jdk.incubator.foreign --enable-native-access=ALL-UNNAMED FFMDemo - Windows:
注意把动态库所在目录加到PATH环境变量,或者运行时指定java.library.path:java --add-modules jdk.incubator.foreign --enable-native-access=ALL-UNNAMED -Djava.library.path=. FFMDemo
预期输出
C函数add(10,20)的返回结果:30
常见问题排查
- 提示找不到函数符号:检查C函数是否加了
static修饰(加了就不会对外暴露符号),动态库命名是否符合系统规范(Linux要lib前缀.so后缀,Windows直接.dll后缀) - 提示孵化器模块错误:确认JDK版本是17及以上,编译运行参数都加了
--add-modules jdk.incubator.foreign - 提示没有本地访问权限:运行参数加
--enable-native-access=ALL-UNNAMED,如果是模块化项目把ALL-UNNAMED换成你的模块名即可
内容的提问来源于stack exchange,提问作者Yogesh Chaudhari
相关产品推荐
相关产品推荐

