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