jextract generation for functional interfaces
Duncan Gittins
duncan.gittins at gmail.com
Mon Apr 7 12:42:07 UTC 2025
I've been using jextract successfully for some time now and it has made it
very easy to call into various Windows. Great work.
One area which seems a bit verbose is the handling of the functional
interface callbacks for upcalls. For example, the qsort compare callback is
as follows (I've shortened the jextract version here):
public class QsortCompareFunc {
QsortCompareFunc() { }
/** The function pointer signature, expressed as a functional interface */
public interface Function {
int apply(MemorySegment _x0, MemorySegment _x1);
}
private static final FunctionDescriptor $DESC = FunctionDescriptor.of(
C_h.C_INT,
C_h.C_POINTER,
C_h.C_POINTER
);
/** The descriptor of this function pointer */
public static FunctionDescriptor descriptor() {
return $DESC;
}
private static final MethodHandle UP$MH =
C_h.upcallHandle(QsortCompareFunc.Function.class, "apply", $DESC);
/**
* Allocates a new upcall stub, whose implementation is defined by {@code
fi}.
* The lifetime of the returned segment is managed by {@code arena}
*/
public static MemorySegment allocate(QsortCompareFunc.Function fi, Arena
arena) {
return Linker.nativeLinker().upcallStub(UP$MH.bindTo(fi), $DESC, arena);
}
private static final MethodHandle DOWN$MH = Linker.nativeLinker
().downcallHandle($DESC);
/**
* Invoke the upcall stub {@code funcPtr}, with given parameters
*/
public static int invoke(MemorySegment funcPtr,MemorySegment _x0,
MemorySegment _x1) {
try {
return (int) DOWN$MH.invokeExact(funcPtr, _x0, _x1);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
}
I made a few changes in jextract to make the callbacks generate interface
rather than a class, adding a helper record Upcall to do most of the
actions:
public interface QsortCompareFunc {
/** The function pointer signature, expressed as a functional interface */
int apply(MemorySegment _x0, MemorySegment _x1);
static final C_h.Upcall<QsortCompareFunc> UPCALL = new
C_h.Upcall<>(QsortCompareFunc.class, "apply", FunctionDescriptor.of(
C_h.C_INT,
C_h.C_POINTER,
C_h.C_POINTER
));
// A new upcall stub is allocated by:
// MemorySegment funcPtr = QsortCompareFunc.UPCALL.allocate(fi,arena)
/**
* Invoke the upcall stub {@code funcPtr}, with given parameters
*/
public static int invoke(MemorySegment funcPtr,MemorySegment _x0,
MemorySegment _x1) {
try {
return (int) UPCALL.downcall().invokeExact(funcPtr, _x0, _x1);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
}
This reduces the definition somewhat, as long as the top level header has a
record to handle all callback interfaces:
public record Upcall<FunctionInterface>(FunctionDescriptor descriptor,
MethodHandle upcall, MethodHandle downcall) {
public Upcall(Class<FunctionInterface> fi, String name, FunctionDescriptor
descriptor) {
this(descriptor, upcallHandle(fi, name, descriptor), Linker.nativeLinker
().downcallHandle(descriptor));
}
/** Find Java method handle of interface for upcall from native -> Java */
public static MethodHandle upcallHandle(Class<?> fi, String name,
FunctionDescriptor fdesc) {
try {
return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
} catch (ReflectiveOperationException ex) {
throw new AssertionError(ex);
}
}
/// Allocate java stub for upcall
public MemorySegment allocate(FunctionInterface javaImpl, Arena scope) {
return Linker.nativeLinker().upcallStub(upcall.bindTo(javaImpl), descriptor,
scope);
}
}
Worth considering?
Duncan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/panama-dev/attachments/20250407/fcc650a9/attachment-0001.htm>
More information about the panama-dev
mailing list