itemis Blog

Calling native code with Java made easy

Written by Jan Mosig | Nov 6, 2023

Java's new Foreign Function & Memory API has been around since Java 14 and is finally approaching the finish line with its third preview in Java 21. However, developer experience might still be a bit rough for use cases like calling a piece of native code outside the JVM. FluffyJ-Memory is a little library that tries to make this as easy as possible.

Granted, calling code outside the JVM is not your daily use-case, there may be situations where this is still required, even in 2023. For example, the other day I wondered how to speak to my Yubikey with Java. I could not find a library that does this but I knew that there is a daemon running on my system as well as a C-based DLL (kindly provided by the nice folks of gpg4win.de) that provides all the necessary functionality. So how about using this library out of my Java application?

I already knew about JNI but found it's usage to be very cumbersome and brittle and was very happy that I wasn't alone with these thoughts. So I encountered Java's new Foreign Function & Memory API which provides a much easier and more modern way to talk to any native code outside the JVM.

Pure FFM Example

You may read about how things work in the JEP. I'll provide you with a little example on how to call the widely known function strlen of the C-Standard-Library directly from Java:

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.ValueLayout;

public class FfmDemo {

    public static void main(final String[] args) throws Throwable {
        final var linker = Linker.nativeLinker();
        final var cStdLib = linker.defaultLookup();
        final var strlenSymbol = cStdLib.find("strlen")
            .orElseThrow(() -> new Exception("Could not find strlen"));
        // strlen takes a pointer and returns an size_t which afaik is a Java long on a 
// 64bit machine final var strlenSig = FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); // create a java handle to strlen final var strlen = Linker.nativeLinker().downcallHandle(strlenSymbol, strlenSig); // arena is a FFM concept that models memory segment lifecycle final var arena = Arena.ofAuto(); // a pointer to a C string final var testStr = arena.allocateUtf8String("example"); // invoke strlen final var result = strlen.invokeWithArguments(testStr); // 7 System.out.println(result); } }

As you can see, that's quite a lot of code to just invoke such an easy to use function. Also it requires quite some knowledge about how FFM works and how calling code in general works.

This gets even harder when it comes to callbacks, as required by the qsort function:

import static java.lang.foreign.Linker.nativeLinker;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.invoke.MethodHandles.lookup;

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodType;

public class FfmDemo {

    public static void main(final String[] args) throws Throwable {
        final var linker = Linker.nativeLinker();
        final var cStdLib = linker.defaultLookup();
        final var qsortSymbol = cStdLib.find("qsort")
            .orElseThrow(() -> new Exception("Could not find qsort"));
        // qsort takes a pointer, two ints and a pointer and returns void
        // the second pointer is a pointer to a callback function that performs element
// comparision final var qsortSig = FunctionDescriptor.ofVoid(ADDRESS, JAVA_INT, JAVA_INT, ADDRESS); // create a java handle to qsort final var qsort = Linker.nativeLinker().downcallHandle(qsortSymbol, qsortSig);
// arena is a FFM concept that models memory segment lifecycle final var arena = Arena.ofAuto(); // allocate an array of 10 bytes final var cBuf =
arena.allocateArray(ValueLayout.JAVA_BYTE, new byte[] {9, 1, 4, 5, 7, 2, 3, 0, 8, 6}); // Method signature of compar function in Java lang final var callbackJavaSig =
MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class); // get a handle to the Java method callback final var callbackJava = lookup().bind(new Comp(), "qsort_compar", callbackJavaSig); // Method signature of compar function in C lang final var callbackCSig =
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS); // Link the Java method to a C function signature and store as a function pointer final var callbackPtr = nativeLinker().upcallStub(callbackJava, callbackCSig, arena); // invoke qsort (buf, bufLen, elmSize, comparator) qsort.invokeWithArguments(cBuf, 10, 1, callbackPtr); // prints 0 1 2 3 4 5 6 7 8 9 cBuf.elements(ValueLayout.JAVA_BYTE).forEach(elm ->
System.out.println(elm.get(ValueLayout.JAVA_BYTE, 0))); } private static class Comp { private int qsort_compar(final MemorySegment left, final MemorySegment right) { final var leftByte = MemorySegment.ofAddress(left.address())
.reinterpret(ValueLayout.JAVA_BYTE.byteSize()) .get(ValueLayout.JAVA_BYTE, 0); final var rightByte = MemorySegment.ofAddress(right.address())
.reinterpret(ValueLayout.JAVA_BYTE.byteSize()) .get(ValueLayout.JAVA_BYTE, 0); var result = 0; if (leftByte < rightByte) { result = -1; } else if (leftByte > rightByte) { result = 1; } return result; } } }

Again there is quite a lot of code and knowledge required to implement such a rather simple sort app.

FluffyJ-Memory to the Rescue

Since speaking with a Yubikey via the DLLs of gpg4win is even harder, I decided to implement a wrapper library that abstracts away many details of the FFM API. I did it also to learn the FFM API which I would have missed if I where to reimplement the smart card and scdaemon APIs in pure Java.The downside is that my library may only support 80% of all use cases and is not as fast as native code. Anyway this is still enough to be able to send commands to the Yubikey.

With FluffyJ the strlen-example from above boils down to this:

 

import static java.lang.foreign.ValueLayout.ADDRESS;
import com.itemis.fluffyj.memory.FluffyNativeMethodHandle;
import java.lang.foreign.Arena;

public class FluffyDemo {

    public static void main(final String[] args) {
        final var strlen =
            FluffyNativeMethodHandle
                .fromCStdLib()
                .returnType(long.class)
                .func("strlen")
                .args(ADDRESS);

        final var testStr = Arena.ofAuto().allocateUtf8String("example");
        // 7
        System.out.println(strlen.call(testStr));
    }
}

Where FluffyNativeMethodHandle is the entry point for constructing a handle to a native function.

The qsort-example would look like this:

 

import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;

import com.itemis.fluffyj.memory.FluffyMemory;
import com.itemis.fluffyj.memory.FluffyNativeMethodHandle;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Arrays;

public class FfmDemo {

    public static void main(final String[] args) {
        // arena is a FFM concept that models memory segment lifecycle
        final var arena = Arena.ofAuto();
        // easy creation of buf seg via FluffyJ-Memory API
        final var bufSeg = FluffyMemory.segment()
.ofArray(new Byte[] {9, 1, 4, 5, 7, 2, 3, 0, 8, 6}).allocate(); // Create a qsort handle final var qsort = FluffyNativeMethodHandle.fromCStdLib() .noReturnType() .func("qsort") .args(ADDRESS, JAVA_INT, JAVA_INT, ADDRESS); // Create a function pointer to the qsort_compar method. final var comparPtr = FluffyMemory.pointer() .toCFunc("qsort_compar") .of(new Comp()) .autoBindTo(arena); // Sort bufSeg qsort.call(bufSeg.address(), 10, 1, comparPtr); // prints 0 1 2 3 4 5 6 7 8 9 Arrays.stream(bufSeg.getValue()).forEach(elm -> System.out.println(elm)); } // Class must at least be protected, method must be public. // This is a FluffyJ-restriction in order for the linker to be able to find the method. protected static class Comp { public int qsort_compar(final MemorySegment left, final MemorySegment right) { // use FluffyJ-Memory API to dereference the pointers. final var leftByte = FluffyMemory.dereference(left).as(Byte.class); final var rightByte = FluffyMemory.dereference(right).as(Byte.class); var result = 0; if (leftByte < rightByte) { result = -1; } else if (leftByte > rightByte) { result = 1; } return result; } } }

Notice how the FluffyJ-Memory API is also used to make working with values, segments and pointers easier.

What about other Libraries?

Until now we have only seen how to use functions from the C-Standard-Library. Of course it is also possible to use functions of any other library, provided that the operating system's linker knows about it.

Have a look at this real world example from jscdLib, which is the library that I use to communicate with a Yubikey via the scdaemon. The names are taken from the official winscard.h documentation.

import static com.itemis.fluffyj.memory.FluffyMemory.pointer;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_LONG;

import com.itemis.fluffyj.memory.FluffyNativeMethodHandle;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;

public class JScdLibExample {

    private static final MemorySegment NULL_PTR = MemorySegment.NULL;

    public static void main(final String[] args) {
// Using confined here to make things thread exclusive
// and make sure that we are using off heap memory. try (var arena = Arena.ofConfined()) { // The scard lib is called winscard on Windows final var winscard = SymbolLookup.libraryLookup("winscard", arena); final var sCardEstablishContext = FluffyNativeMethodHandle .fromCLib(winscard) .returnType(long.class) .func("SCardEstablishContext") .args(JAVA_LONG, ADDRESS, ADDRESS, ADDRESS); // The Windows variety of SCardListReaders ends with an A. // Other OS just use SCardListReaders without A. final var scardListReaders = FluffyNativeMethodHandle .fromCLib(winscard) .returnType(long.class) .func("SCardListReadersA") .args(ADDRESS, ADDRESS, ADDRESS, ADDRESS); final var scardFreeMemory = FluffyNativeMethodHandle .fromCLib(winscard) .returnType(long.class) .func("SCardFreeMemory") .args(ADDRESS, ADDRESS); final var scardReleaseContext = FluffyNativeMethodHandle .fromCLib(winscard) .returnType(long.class) .func("SCardReleaseContext") .args(ADDRESS); final var phContext = pointer().allocate(arena); // First arg is SCARD_SCOPE_SYSTEM sCardEstablishContext.call(2, NULL_PTR, NULL_PTR, phContext.address()); final var hContext = phContext.getValue(); final var mszReaders = pointer().of(String.class).allocate(arena); final var pcchReaders = arena.allocate(ValueLayout.ADDRESS); pcchReaders.set(ValueLayout.JAVA_INT, 0, -1); scardListReaders.call(hContext, NULL_PTR, mszReaders.address(), pcchReaders);
// Not entirely sure what happens on other machines and OSs if there are no
// SmartCard reader devices available, so please be careful. final var readersMultiStrPtr = mszReaders.rawDereference();
// "Scroll" through the C-multi-string by calling getUtf8String multiple times.
// This method will always read until \0 is encountered. final var readerOne = readersMultiStrPtr.getUtf8String(0); final var readerTwo = readersMultiStrPtr.getUtf8String(readerOne.length() + 1L); System.err.println(readerOne); System.err.println(readerTwo); scardFreeMemory.call(hContext, mszReaders.address()); scardReleaseContext.call(hContext); } } }

A Note of Warning

Please be warned:

1. Fiddling around with unprotected off heap memory areas is by definition unsafe. The JVM may not protect you from accessing memory that does not belong to you and vice versa.

2. Performance of "Java based" FFM code is very much likely to be way worse than native code, i. e. quick sorting a large array via FFM is much slower than doing it directly in a compiled C-program. So please think twice if performance matters in your project.

Conclusion

So much for our little show case of an easy way to access non JVM based native code and memory. Maybe it'll help you working with your beloved C-libraries. Of course libraries written in any other language are virtually also supported. I guess FluffyJ-Memory could even be used to generate easy to read code directly from header files. However, it is not finished yet due to lack of time. Please feel free to provide pull requests if you find this useful.