Project Panama

https://openjdk.org/projects/panama/ https://docs.oracle.com/en/java/javase/19/core/foreign-function-and-memory-api.html#GUID-FBE990DA-C356-46E8-9109-C75567849BA8

Project Panama 是 OpenJDK 的一个项目,旨在改善 Java 与本地代码(native code)之间的互操作性。 简化 Java 与本地库的交互,同时提高性能。Project Panama 引入了许多新的概念和对象

正式发布于 JDK 22 : JEP 454: Foreign Function & Memory API

The Foreign Function & Memory API (FFM API) defines classes and interfaces so that client code in libraries and applications can

The FFM API resides in the java.lang.foreign package of the java.base module.

As a brief example of using the FFM API, here is Java code that obtains a method handle for a C library function radixsort and then uses it to sort four strings which start life in a Java array (a few details are elided).

// 1. Find foreign function on the C library path
Linker linker          = Linker.nativeLinker();
SymbolLookup stdlib    = linker.defaultLookup();
MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. Use try-with-resources to manage the lifetime of off-heap memory
try (Arena offHeap = Arena.ofConfined()) {
    // 4. Allocate a region of off-heap memory to store four pointers
    MemorySegment pointers
        = offHeap.allocate(ValueLayout.ADDRESS, javaStrings.length);
    // 5. Copy the strings from on-heap to off-heap
    for (int i = 0; i < javaStrings.length; i++) {
        MemorySegment cString = offHeap.allocateFrom(javaStrings[i]);
        pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
    }
    // 6. Sort the off-heap data by calling the foreign function
    radixsort.invoke(pointers, javaStrings.length, MemorySegment.NULL, '\0');
    // 7. Copy the (reordered) strings from off-heap to on-heap
    for (int i = 0; i < javaStrings.length; i++) {
        MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
        javaStrings[i] = cString.reinterpret(...).getString(0);
    }
} // 8. All off-heap memory is deallocated here
assert Arrays.equals(javaStrings,
                     new String[] {"car", "cat", "dog", "mouse"});  // true

关键概念

Project Panama 核心概念

一、核心 API:Foreign Function & Memory (FFM)

概念对象/方法作用示例代码片段
本地内存管理MemorySegment表示一块本地内存区域,支持自动资源管理(通过 Arena)。MemorySegment segment = MemorySegment.allocateNative(100, arena);
内存布局描述MemoryLayout描述内存结构(如结构体、数组),用于类型安全的内存访问。MemoryLayout structLayout = MemoryLayout.structLayout(JAVA_INT, JAVA_INT);
内存生命周期管理Arena管理 MemorySegment 的生命周期(自动释放或手动作用域控制)。try (Arena arena = Arena.ofConfined()) { ... }
本地函数链接器Linker绑定 Java 与本地函数,支持跨平台调用。Linker linker = Linker.nativeLinker();
符号查找SymbolLookup查找本地库中的函数或全局变量。SymbolLookup stdlib = linker.defaultLookup();
SymbolLookup lookup = SymbolLookup.libraryLookup("D:/my.dll", tempArena)
函数描述符FunctionDescriptor定义本地函数的签名(参数类型 + 返回类型)。FunctionDescriptor funcDesc = FunctionDescriptor.of(JAVA_INT, ADDRESS);
本地函数调用Linker.downcallHandle()将本地函数绑定为 Java 可调用的 MethodHandleMethodHandle handle = linker.downcallHandle(symbol, funcDesc);
回调函数指针生成Linker.upcallStub()将 Java 方法转换为本地函数指针(供 C 调用)。MemorySegment callbackPtr = linker.upcallStub(handle, desc, arena);
结构化内存访问VarHandle类型安全地读写 MemorySegment 中的字段。VarHandle xHandle = structLayout.varHandle(PathElement.groupElement("x"));
类型化内存访问ValueLayout定义基本数据类型的内存布局(如 JAVA_INT, ADDRESS)。ValueLayout intLayout = ValueLayout.JAVA_INT;
内存地址操作MemoryAddress表示指向内存的地址(通常由 MemorySegment 管理)。MemoryAddress address = segment.address();
堆内存分配SegmentAllocator分配堆外内存的通用接口(Arena 实现了此接口)。SegmentAllocator allocator = Arena.global();
异常处理UnsupportedOperationException当操作违反内存安全或平台限制时抛出(如越界访问)。try { segment.get(JAVA_INT, 100); } catch (Exception e) { ... }
向量化操作Vector支持 SIMD 指令的向量计算(与 Panama 结合优化本地代码性能)。IntVector vector = IntVector.fromArray(IntVector.SPECIES_256, array, 0);

二、内存管理

1. 内存分配

// 在 Arena 中分配堆外内存(自动释放)
try (Arena arena = Arena.ofConfined()) {
    MemorySegment buffer = arena.allocate(1024);  // 分配 1KB
    buffer.set(ValueLayout.JAVA_INT, 0, 42);      // 写入 int 值
}

2. 结构化内存访问

// 定义结构体布局
final MemoryLayout LAYOUT = MemoryLayout.structLayout(
		ValueLayout.JAVA_INT.withName("x"),
		ValueLayout.JAVA_INT.withName("y")
);
 
// 创建 VarHandle 访问字段
VarHandle xHandle = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("x"));
VarHandle yHandle = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("y"));
 
// 写入/读取结构体
try (Arena arena = Arena.ofConfined()) {
	MemorySegment segment = arena.allocate(LAYOUT);
	//设置 这里的0L是相对偏移量, 大部分都是0?
	xHandle.set(segment, 0L, 10);  // 等价于 segment.set(ValueLayout.JAVA_INT, 0, 10)
	yHandle.set(segment, 0L, 20);  // 等价于 segment.set(ValueLayout.JAVA_INT, 4, 20)
	//读取 by VarHandle
	Object xx = xHandle.get(segment, 0);//
	Object yy = yHandle.get(segment, 0);
	System.out.println(" xx = "+ xx +", yy = "+yy);
	//读取 by MemorySegment
	int x = segment.get(ValueLayout.JAVA_INT, 0);// 0 是偏移值
	int y = segment.get(ValueLayout.JAVA_INT, 4);// 4 是偏移值
	System.out.println(" x = "+ x +", y = "+y);// x = 10, y = 20
}

基础类型对照表

Java ValueLayoutC/C++ 类型大小 (Bytes)对齐要求典型使用场景
JAVA_BYTEint8_t / char11处理二进制数据、字节流
JAVA_SHORTint16_t / short22短整数、Unicode 字符 (UTF-16)
JAVA_INTint32_t / int44常规整数、错误代码
JAVA_LONGint64_t / long88大整数、时间戳
JAVA_FLOATfloat44单精度浮点数
JAVA_DOUBLEdouble88双精度浮点数
JAVA_CHARwchar_t (C++11+)2 (Win) /4 (Unix)2/4宽字符处理 (如Windows API)
JAVA_BOOLEANbool (C++)11布尔标志位
ADDRESSvoid* / char*4 (32位) /8 (64位)按平台指针、字符串、回调函数指针

关于 const char * 的访问

返回的const char*指向的是静态内存区域,而Java的MemorySegment默认不知道这个区域的长度,导致在尝试读取时无法确定边界;

就 ** 的离谱!!

 
MemorySegment result = (MemorySegment) getenv.invoke(varName);  
//重新划定 MemorySegment 内存的大小
MemorySegment validSegment = result.reinterpret(Long.MAX_VALUE, arena, null);
 
// 读取   
private static String readCString(MemorySegment cStr) {  
    StringBuilder str = new StringBuilder();  
    long length = 0;  
    byte b;  
    while ((b = cStr.get(ValueLayout.JAVA_BYTE, length)) != 0) {  
        str.append((char) b);  
        length++;  
    }  
    return str.toString();  
}

嵌套的结构体

C 定义

 
typedef struct {
    int idisable;   /* 0 = "support this feature", 1 = "not support" */
    int imin;       /* minimum value */
    int imax;       /* maximum value */
    int idef;       /* default value */
} ToupnamRange;
 
typedef struct {
    char            id[64];         /* unique camera id, used for Toupnam_Open */
    char            sn[64];         /* serial number */
    char            name[64];
    char            model[64];
    char            version[64];
    char            addr[64];       /* ip */
    char            url[256];       /* playback url, such as rtsp://xxxx/yyyy */
    unsigned        state;          /* TOUPNAM_STATE_xxx */
    unsigned        flag;           /* TOUPNAM_FLAG_xxx */
    ToupnamRange    range[64];
} ToupnamDevice;
 
 
/* enumerate the cameras discovered by the computer, return the number
    sz: size of the array
    when sz is too small, return value will be greater than sz, means that the caller must use bigger array
*/
TOUPNAM_API(unsigned) Toupnam_Enum(ToupnamDevice arr[], unsigned sz);

FFI 定义

ToupnamRange.java

public class ToupnamRange {
    public static final GroupLayout LAYOUT = MemoryLayout.structLayout(
        ValueLayout.JAVA_INT.withName("idisable"),
        ValueLayout.JAVA_INT.withName("imin"),
        ValueLayout.JAVA_INT.withName("imax"),
        ValueLayout.JAVA_INT.withName("idef")
    );
 
    private static final VarHandle
        IDISABLE = LAYOUT.varHandle(PathElement.groupElement("idisable")),
        IMIN = LAYOUT.varHandle(PathElement.groupElement("imin")),
        IMAX = LAYOUT.varHandle(PathElement.groupElement("imax")),
        IDEF = LAYOUT.varHandle(PathElement.groupElement("idef"));
 
    private final MemorySegment segment;
 
    public ToupnamRange(MemorySegment segment) {
        this.segment = segment;
    }
 
    public boolean isSupported() {
        return (int) IDISABLE.get(segment, 0L) == 0;
    }
 
    public int min() {
        return (int) IMIN.get(segment, 0L);
    }
 
    public int max() {
        return (int) IMAX.get(segment, 0L);
    }
 
    public int defaultValue() {
        return (int) IDEF.get(segment, 0L);
    }
}

ToupnamDevice.java

public class ToupnamDevice {  
    private static final int  
            STRING_LEN_64 = 64,  
            STRING_LEN_256 = 256,  
            TOUPNAM_MAX = 64;  
  
    // 定义结构体布局  
    public static final GroupLayout LAYOUT = MemoryLayout.structLayout(  
            MemoryLayout.sequenceLayout(STRING_LEN_64, ValueLayout.JAVA_BYTE).withName("id"),  
            MemoryLayout.sequenceLayout(STRING_LEN_64, ValueLayout.JAVA_BYTE).withName("sn"),  
            MemoryLayout.sequenceLayout(STRING_LEN_64, ValueLayout.JAVA_BYTE).withName("name"),  
            MemoryLayout.sequenceLayout(STRING_LEN_64, ValueLayout.JAVA_BYTE).withName("model"),  
            MemoryLayout.sequenceLayout(STRING_LEN_64, ValueLayout.JAVA_BYTE).withName("version"),  
            MemoryLayout.sequenceLayout(STRING_LEN_64, ValueLayout.JAVA_BYTE).withName("addr"),  
            MemoryLayout.sequenceLayout(STRING_LEN_256, ValueLayout.JAVA_BYTE).withName("url"),  
            ValueLayout.JAVA_INT.withName("state"),  
            ValueLayout.JAVA_INT.withName("flag"),  
            MemoryLayout.sequenceLayout(TOUPNAM_MAX, ToupnamRange.LAYOUT).withName("range")  
    );  
  
    // 仅对值布局字段使用 VarHandle    
    private static final VarHandle  
            STATE = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("state")),  
            FLAG = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("flag"));  
  
    // 字符串字段通过偏移量访问  
    private static final long  
            ID_OFFSET = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("id")),  
            SN_OFFSET = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("sn")),  
            NAME_OFFSET = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("name")),  
            MODEL_OFFSET = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("model")),  
            VERSION_OFFSET = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("version")),  
            ADDR_OFFSET = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("addr")),  
            URL_OFFSET = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("url"));  
  
    private final MemorySegment segment;  
  
    public ToupnamDevice(MemorySegment segment) {  
        this.segment = segment;  
    }  
  
    ///////////////////////////////////////////  
    // 字段访问方法  
    ///////////////////////////////////////////  
  
    /* 字符串字段 */
    public String id() {  
        return getString(ID_OFFSET);  
    }  
  
    public String sn() {  
        return getString(SN_OFFSET);  
    }  
    ... 省略 ...
    /* 整型字段 */
    public int state() {  
        return (int) STATE.get(segment, 0L);  
    }  
  
    public int flag() {  
        return (int) FLAG.get(segment, 0L);  
    }  
  
    ///////////////////////////////////////////  
    // 辅助方法  
    ///////////////////////////////////////////  
  
    private String getString(long offset) {  
        return segment.asSlice(offset).getString(offset);  
    }  
  
    private void setString(long offset, String value, int maxLength) {  
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);  
        int length = Math.min(bytes.length, maxLength - 1);  
        MemorySegment.copy(  
                MemorySegment.ofArray(bytes), 0,  
                segment.asSlice(offset), 0, length  
        );  
        segment.set(ValueLayout.JAVA_BYTE, offset + length, (byte)0);  
    }  
  
    /* 嵌套结构体数组 */
    public ToupnamRange[] ranges() {  
        long rangeOffset = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("range"));  
        long elementSize = ToupnamRange.LAYOUT.byteSize();  
        ToupnamRange[] ranges = new ToupnamRange[TOUPNAM_MAX];  
        for (int i = 0; i < TOUPNAM_MAX; i++) {  
            ranges[i] = new ToupnamRange(  
                    segment.asSlice(rangeOffset + i * elementSize, elementSize)  
            );  
        }  
        return ranges;  
    }  
}

FFI 数组结构体 创建和访问

try (Arena arena = Arena.ofConfined()) {
	// 1. 分配包含3个结构体的数组内存 
	// 类似 cpp 的 ToupnamDevice[3]
    MemorySegment devicesMem = arena.allocate(
        MemoryLayout.sequenceLayout(3, ToupnamDevice.layout())
    );
    // 2. 计算单个元素字节大小
    long elementSize = ToupnamDevice.layout().byteSize();
 
    // 3. 遍历访问每个元素
    for (int i = 0; i < 3; i++) {
        // 计算当前元素偏移量
        long offset = i * elementSize;
        
        // 4. 获取当前元素的内存段
        MemorySegment currentElement = devicesMem.asSlice(offset, elementSize);
        
        // 5. 包装成ToupnamDevice对象
        ToupnamDevice device = new ToupnamDevice(currentElement);
 
        // 6. 访问具体字段
        System.out.println("Device " + i + " ID: " + device.id());
        System.out.println("Device " + i + " IP: " + device.addr());
        
        // 7. 访问嵌套的range数组(示例访问第一个range)
        ToupnamRange firstRange = device.range()[0];
        System.out.printf("Range 0: %d-%d (default: %d)\n",
            firstRange.imin(), firstRange.imax(), firstRange.idef());
    }
}

实例 调用一个工业相机SDK

声明

 
/*
    when toupnam.dll/libtoupnam.so discovery new camera, pCallback will be called.
    pCallback can be NULL if the application does not interest this.
    Toupnam_Init: call only once when application startup
    Toupnam_Fini: call only once when application exit
*/
TOUPNAM_API(void) Toupnam_Init(PTOUPNAM_EVENT_CALLBACK pCallback, void* pCallbackCtx);
 
/* enumerate the cameras discovered by the computer, return the number
    sz: size of the array
    when sz is too small, return value will be greater than sz, means that the caller must use bigger array
*/
TOUPNAM_API(unsigned) Toupnam_Enum(ToupnamDevice arr[], unsigned sz);
 

对应 FFI

 
    // 初始化函数
    public static void init(ToupnamInit.ToupnamEventListener listener) {
        try (Arena arena = Arena.ofConfined()) {
            // 创建回调代理
            MethodHandle proxy = MethodHandles.lookup().findVirtual(
                    ToupnamInit.CallbackProxy.class, "invoke",
                    MethodType.methodType(void.class, int.class, int.class, MemorySegment.class, MemorySegment.class)
            );
            // 分配回调内存 upcal
            MemorySegment callback = LINKER.upcallStub(
                    proxy.bindTo(new ToupnamInit.CallbackProxy(listener)),
                    FunctionDescriptor.ofVoid(
                            ValueLayout.JAVA_INT,
                            ValueLayout.JAVA_INT,
                            ValueLayout.ADDRESS,
                            ValueLayout.ADDRESS
                    ),
                    arena
            );
 
            SymbolLookup lookup = SymbolLookup.libraryLookup(UVCHAM_DLL, arena);
            // 获取初始化函数句柄 downcall
            MethodHandle initHandle = LINKER.downcallHandle(
                    lookup.find("Toupnam_Init").orElseThrow(),
                    FunctionDescriptor.ofVoid(
                            ValueLayout.ADDRESS,
                            ValueLayout.ADDRESS
                    )
            );
 
            // 调用初始化函数
            initHandle.invokeExact(callback, MemorySegment.NULL);
 
            List<ToupnamDevice> toupnamDevices = ToupnamCamera.enumerateDevices(arena);
            if (toupnamDevices.isEmpty() ){
                System.out.println("not devices found ");
            }
 
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException("Init failed", e);
        }
    }
    
    public static List<ToupnamDevice> enumerateDevices(Arena tempArena) {
        List<ToupnamDevice> devices = new ArrayList<>();
        try{
            int MAX = 3;
            // 分配设备数组内存
            MemorySegment devicesMem = tempArena.allocate(
                    MemoryLayout.sequenceLayout(MAX, ToupnamDevice.LAYOUT)
            );
            // 获取枚举函数
            SymbolLookup lookup = SymbolLookup.libraryLookup(UVCHAM_DLL, tempArena);
            // 获取函数句柄
            MethodHandle enumHandle = LINKER.downcallHandle(
                    lookup.find("Toupnam_Enum").orElseThrow(),
                    FunctionDescriptor.of(
                            ValueLayout.JAVA_INT,
                            ValueLayout.ADDRESS,
                            ValueLayout.JAVA_INT
                    )
            );
            // 调用枚举函数
            int count = (int) enumHandle.invoke(devicesMem, MAX);
            log.info(" find count = {} ", count);
            // 解析设备信息
            count = Math.min(count, MAX);
            for (int i = 0; i < count; i++) {
                MemorySegment deviceMem = devicesMem.asSlice(i * ToupnamDevice.LAYOUT.byteSize());
                ToupnamDevice device = new ToupnamDevice(deviceMem);
                devices.add(device);
            }
            System.out.println("toupnamDevices = "+ devices);
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException("Device enumeration failed", e);
        }
        return devices;
    }
代码归档

Transclude of ToupnamCamera.zip


三、本地函数调用

1. Downcall(Java → C)

// 查找本地函数符号
SymbolLookup stdlib = linker.defaultLookup();
MemorySegment printfSymbol = stdlib.find("printf").orElseThrow();
 
// 定义函数描述符
FunctionDescriptor printfDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_INT,    // 返回值类型(int)
    ValueLayout.ADDRESS      // 参数类型(char*)
);
 
// 绑定并调用
MethodHandle printfHandle = linker.downcallHandle(printfSymbol, printfDesc);
try (Arena arena = Arena.ofConfined()) {
    MemorySegment msg = arena.allocateUtf8String("Hello from Java!\n");
    printfHandle.invoke(msg);  // 输出字符串
}
 
 
// 对于 返回值为void的函数
MemorySegment callback = LINKER.upcallStub(  
	FunctionDescriptor.ofVoid(  
			ValueLayout.JAVA_INT,  
			ValueLayout.JAVA_INT,  
			ValueLayout.ADDRESS,  
			ValueLayout.ADDRESS  
	),  
	arena  
);
 

函数指针 回调

函数原型

 
typedef struct {
    int         result;
    unsigned    length;
    void*       ptr;
    void*       ctx;
} ToupnamEventExtra;
 
// PTOUPNAM_EVENT_CALLBACK 函数指针的定义
typedef void (__stdcall* PTOUPNAM_EVENT_CALLBACK)(unsigned nEvent, unsigned nPara, void* pCallbackCtx, ToupnamEventExtra* pExtra);
 
 
/* 
HToupnam 是相机句柄
截图并回调 pCaptureCallback
   pstrFileName:
                NULL        -> capture image and then return by callback
                "raw"       -> capture raw image and then return by callback
                "abc.jpg"   -> capture image and then save it in the camera sd card with filename 'abc.jpg'
                "abc.raw"   -> capture raw image and then save it in the camera sd card with filename 'abc.raw'
                "thumbnail" -> capture the thumbnail image and then return by callback
                "*"         -> capture image and then save it in the camera sd card with auto generated file name
                "*.raw"     -> capture raw image and then save it in the camera sd card with auto generated file name
*/
TOUPNAM_API(HRESULT)  Toupnam_Capture(HToupnam h, const char* pstrFileName, PTOUPNAM_CAPTURE_CALLBACK pCaptureCallback, void* pCallbackCtx);

对应 Java FFI

  //  Capture java 回调接口
public interface CaptureCallback {
	void onCapture(int result, MemorySegment pData, long nLength, MemorySegment imageData, MemorySegment ctx);
}
 
 
public synchronized void Capture() {
 
   final CaptureCallback proxyObj = new CaptureCallback() {  
		@Override  
		public void onCapture(int result, MemorySegment pData, long nLength, MemorySegment imageData, MemorySegment ctx) {  
			//打印全部参数  
			log.info("Capture fileName={}, result = {}, pData={}, nLength={}, imageData={}, ctx={}"  
					,fileName, result, pData, nLength, imageData, ctx);  
		}  
	};
	 //java 回调对象的描述
	MethodHandle proxy = MethodHandles.lookup().findVirtual(
			CaptureCallback.class, "onCapture",
			MethodType.methodType(void.class, int.class, MemorySegment.class, long.class,
					MemorySegment.class, MemorySegment.class)
	);
	// 分配 PTOUPNAM_CAPTURE_CALLBACK 的本地内存, 绑定到Java 供C/C++调用;
	MemorySegment callback = LINKER.upcallStub(  
	        proxy.bindTo(proxyObj),//绑定 java 回调对象
	        FunctionDescriptor.ofVoid(  
	                ValueLayout.JAVA_INT,  
	                ValueLayout.ADDRESS,  
	                ValueLayout.JAVA_LONG,  
	                ValueLayout.ADDRESS,  
	                ValueLayout.ADDRESS  
	        ),  
	        arena  
	);
	//查找 DLL 的 Toupnam_Capture 函数指针
	MethodHandle Toupnam_Capture =LINKER.downcallHandle(  
	        lookup.find("Toupnam_Capture").orElseThrow(),  
	        FunctionDescriptor.ofVoid(  
	                ValueLayout.ADDRESS,           // HToupnam h  
	                ValueLayout.ADDRESS,           // const char* pstrFileName  
	                ValueLayout.ADDRESS,           // PTOUPNAM_CAPTURE_CALLBACK  
	                ValueLayout.ADDRESS            // void* pCallbackCtx  
	        )  
	);
	//调用
	Toupnam_Capture.invokeExact(DEVICE_HWND, MemorySegment.NULL, callback, MemorySegment.NULL);  
	Thread.sleep(500);
 
}
 

2. Upcall 传递函数指针(C → Java函数)

// 定义 Java 回调函数
interface Callback {
    int add(int a, int b);
}
Callback adder = (a, b) -> a + b;
 
// 生成函数指针
MethodHandle adderHandle = MethodHandles.lookup()
    .findVirtual(Callback.class, "add", MethodType.methodType(int.class, int.class, int.class));
MemorySegment adderPtr = linker.upcallStub(
    adderHandle.bindTo(adder),
    FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT),
    Arena.global()
);

示例

编译 DLL

一个简单的 DLL,导出一个加法函数 int add(int a, int b)

#include <stdio.h>
 
// 确保使用 extern "C" 避免 C++ 名称修饰
#ifdef __cplusplus
extern "C" {
#endif
 
__declspec(dllexport) int add(int a, int b) {
    return a + b;
}
 
#ifdef __cplusplus
}
#endif

调用 DLL

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
 
public static void main(String[] args) {  
    // 1. 自动内存管理  
    try (Arena arena = Arena.ofConfined()) {  
        // 2. 加载 DLL        Linker linker = Linker.nativeLinker();  
        SymbolLookup lookup = SymbolLookup.libraryLookup("math.dll", (Arena) arena.scope());  
  
        // 3. 查找函数符号  
        MemorySegment addFunc = lookup.find("add").orElseThrow();  
  
        // 4. 定义函数描述符(参数和返回值类型)  
        FunctionDescriptor descriptor = FunctionDescriptor.of(  
                ValueLayout.JAVA_INT,  // 返回值类型  
                ValueLayout.JAVA_INT,  // 参数1  
                ValueLayout.JAVA_INT   // 参数2  
        );  
  
        // 5. 绑定方法句柄  
        MethodHandle addHandle = linker.downcallHandle(addFunc, descriptor);  
  
        // 6. 调用函数  
        int result = (int) addHandle.invoke(3, 5);  
        System.out.println("Result: " + result); // 输出 8    
    }
}