Contents

Foreign Function Utilities

Written by: David Vlijmincx

Introduction

In this post, we'll explore how to make downcall handles more user-friendly and performant at the same time. I was watching this talk on youtube by Per Minborg on the FFM API, where he demonstrated a handy utility class that significantly simplifies working with foreign functions. Since I couldn't find a complete code example of this approach, I've recreated it here along with practical examples showing how it works with records.

The Foreign Function & Memory (FFM) API is powerful, but the standard approach can involve quite a bit of boilerplate code. This utility method helps streamline the process while maintaining all the performance benefits.

How You Would Do It Normally

Let's start by looking at how most tutorials (including my own previous posts) demonstrate making a downcall to a native function:

1
2
3
4
5
6
7
8
Linker linker = Linker.nativeLinker();

MethodHandle strlen = linker.downcallHandle(linker.defaultLookup().findOrThrow("strlen"),
        FunctionDescriptor.of(JAVA_INT, ADDRESS));

try (Arena arena = Arena.ofConfined()) {
    System.out.println("length = " + (int) strlen.invokeExact(arena.allocateFrom("Hello, World!")));
}

This approach works perfectly fine, but it requires several steps:

  1. Create a Linker instance
  2. Look up the native symbol by name
  3. Create a MethodHandle with the appropriate function descriptor
  4. Call invokeExact() with proper casting

While this gives you full control, it becomes repetitive when you need to work with multiple native functions. The boilerplate code can quickly accumulate, making your codebase harder to maintain.

FFM Utility Method

The utility method is straightforward but tremendously helpful. It allows you to create a downcall by passing a functional interface, native method name, and FunctionDescriptor. This approach removes much of the boilerplate required for creating and calling downcalls:

1
2
3
4
5
6
7
8
9
 static <T> T link(Class<T> type,
                      String name,
                      FunctionDescriptor descriptor) {

    Linker linker = Linker.nativeLinker();
    MemorySegment symbol = linker.defaultLookup().findOrThrow(name);
    MethodHandle handle = linker.downcallHandle(symbol, descriptor);
    return MethodHandleProxies.asInterfaceInstance(type, handle);
}

The magic happens in the MethodHandleProxies.asInterfaceInstance(type, handle) call. This method creates a dynamic proxy that implements your functional interface, automatically routing method calls to the underlying MethodHandle. This means you can call your native function as if it were a regular Java method, with proper type safety and without manual casting.

You can use this utility like this: link(StringLength.class, "strlen", FunctionDescriptor.of(JAVA_INT, ADDRESS)). This produces the same functionality as the first example but with significantly less boilerplate code. The approach becomes especially valuable when you need multiple downcalls in your application.

1
2
3
4
5
6
7
8
try (Arena arena = Arena.ofConfined()) {
    StringLength strlen = link(StringLength.class, "strlen", FunctionDescriptor.of(JAVA_INT, ADDRESS));
    System.out.println("strlen = " + strlen.strlen(arena.allocateFrom("Hello, World!")));
}

public interface StringLength {
    int strlen(MemorySegment str);
}

Notice how much cleaner this is: we define a simple interface that matches the native function signature, and then we can call it directly without any casting or invokeExact() complexity. The interface provides compile-time type safety and makes the code much more readable.

FFM with Records

When you're going to reuse downcall handles, consider storing them in a record. Records are trusted by the JVM, which allows it to perform optimizations that wouldn't be possible with regular classes. Here's how you can restructure the previous example to use records.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
record NativeDispatcher(StringLength stringLength){

    public int getStringLength(String str) {
        try (Arena arena = Arena.ofConfined()) {
            return stringLength.strlen(arena.allocateFrom(str));
        }
    }
}

// Usage
NativeDispatcher dispatcher = new NativeDispatcher(link(StringLength.class, "strlen", FunctionDescriptor.of(JAVA_INT, ADDRESS)));
System.out.println("length = " + dispatcher.getStringLength("Hello, World!"));

This pattern provides several benefits:

  • Encapsulation: The record hides the complexity of memory management and native calls
  • Performance: The JVM can optimize record access more aggressively
  • Reusability: You create the downcall handle once and reuse it multiple times
  • Type Safety: The record provides a clean, typed interface to your native functions

The record approach is particularly useful when you have multiple related native functions that you want to group together, creating a clean facade for an entire native library.

Conclusion

The FFM utility method simplifies working with Java's Foreign Function Interface while maintaining all the performance benefits. By combining this approach with records, you can create clean, efficient, and reusable interfaces to native code that feel natural to use from other Java code. This pattern is especially valuable for applications that make frequent native calls or work with complex native libraries. The reduced boilerplate makes your code more maintainable, while the record-based organization provides better performance characteristics and cleaner APIs.