代码对比
✕ Java 1.1+
public class CallCFromJava {
static { System.loadLibrary("strlen-jni"); }
public static native long strlen(String s);
public static void main(String[] args) {
long ret = strlen("Bambi");
System.out.println("Return value " + ret); // 5
}
}
// Run javac -h to generate the .h file, then write C:
// #include "CallCFromJava.h"
// #include <string.h>
// JNIEXPORT jlong JNICALL Java_CallCFromJava_strlen(
// JNIEnv *env, jclass clazz, jstring str) {
// const char* s = (*env)->GetStringUTFChars(env, str, NULL);
// jlong len = (jlong) strlen(s);
// (*env)->ReleaseStringUTFChars(env, str, s);
// return len;
// }
✓ Java 22+
void main() throws Throwable {
try (var arena = Arena.ofConfined()) {
// Use any system library directly — no C wrapper needed
var stdlib = Linker.nativeLinker().defaultLookup();
var foreignFuncAddr = stdlib.find("strlen").orElseThrow();
var strlenSig = FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
var strlenMethod = Linker.nativeLinker() .downcallHandle(foreignFuncAddr, strlenSig);
var ret = (long) strlenMethod.invokeExact(arena.allocateFrom("Bambi"));
System.out.println("Return value " + ret); // 5
}
}
// Your own C library needs no special Java annotations:
// long greet(char* name) {
// printf("Hello %s\n", name);
// return 0;
// }
发现此代码有问题? 告诉我们。
为什么现代方式更好
C 代码保持纯 C
C 函数无需 JNI 注解或 JNIEnv 样板——任何现有的 C 库都可以直接调用。
更灵活
无需编写适配器代码或生成头文件,即可直接调用大多数现有的 C/C++ 库。
工作流更简便
无需停下来运行 javac -h,也无需实现生成的 .h 文件中定义的接口。
旧方式
JNI(Java 原生接口)
现代方式
FFM(外部函数与内存 API)
自 JDK
22
难度
高级
JDK 支持
从 Java 调用 C 代码
可用
在 JDK 22 中标准化(2024 年 3 月);此前自 JDK 14 起处于孵化阶段
工作原理
Java 有两种调用原生 C/C++ 代码的方式:传统的 JNI 和现代的 FFM API。使用 JNI,需要将方法声明为 native,运行 javac -h 生成 C 头文件,然后使用繁琐的 JNI C API(JNIEnv、jstring 等)实现函数。FFM 在 Java 22 中作为标准 API 引入,消除了这一切:C 代码就是普通的 C——无需 JNI 约定。这使得调用现有 C/C++ 库无需修改变得更加容易。Java 端使用 Arena 进行安全的堆外内存管理,使用 MethodHandle 进行降调用,确保灵活性和安全性。
相关文档
证明