Contents

Call C method from Java

Writen by: David Vlijmincx

Introduction

In this post, we are going to take a look at how to call a C method in Java by using the foreign function API. This API lets you make down calls to a C library in a safe way. To use the code from this tutorial you need to use Java 22 or later.

Calling a C method from Java

To call a C method you first need to create an Arena, this lets you create a continuous region in memory. You will need this to pass the parameters to the C method.

Creating an Arena is done like this:

1
2
3
try (var arena = Arena.ofConfined()){

}

I like to use them in a try-with-resource statement, so they are closed when they are done. Otherwise, you would have to call the close method yourself. There are 4 types of Arenas:

  • ofConfined: This arena is alive till it is closed and can only be accessed by the thread that created it.
  • ofShared: This arena is alive till the close method is called, the arena can be accessed by multiple threads.
  • ofAuto: This arena's lifetime is managed by the garbage collector and can be accessed by multiple threads.
  • global: MemorySegments created in this arena are never deallocated and the arena has an unbounded lifetime.

The next step is to create a linker and to make a methodHandle. The Linker does the heavy lifting when creating down calls, it knows the calling conventions of the platform to call the C methods. To create a MethodHandle with the linker we need to know the name of the C method and the method signature. In this post, we will make a downcall to the strcmp method. It has the following signature:

1
int strcmp (const char* str1, const char* str2);

The strcmp method takes two String values as parameters and returns an int value. The int value represents which string is different:

  • 0 : both String are equal
  • >0 : If the first not matching char is found in the first string
  • <0: If the first not matching char is found in the second string

Creating a MethodHandle is done as follows.

1
2
3
4
MethodHandle strcmp = linker.downcallHandle(
        linker.defaultLookup().find("strcmp").orElseThrow(),
        FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS)
);

The first parameter of downcallHandle() uses the Linker to find the method inside standard loaded C libraries with the name “strcmp”. The second parameter is the FunctionDescriptor, this is the signature of the method we are looking for. The first value of the FunctionDescriptor is the return value, and the following values are the parameters.

The next step is to create two strings that can be passed to the strcmp method. To create a string in native memory that is safe to pass to the C method we need to use arena.allocateFrom(). The allocateFrom method is a handy method to create a memorySegment for String values. You can use it like this:

1
2
MemorySegment string1 = arena.allocateFrom("String");
MemorySegment string2 = arena.allocateFrom("String");

This creates two MemorySegments that each store a string. These Strings don't live on the Java heap but in native memory, which C can safely access. The next step is to invoke the strcmp method handle which you do like this:

1
int invoke = (int) strcmp.invoke(string1, string2);

This makes the call to the C method and returns an int value. In this case, it is safe to cast it to an int because the strcmp returns an int value.

Complete code

Below you will find the complete code example used in this tutorial.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.davidvlijmincx.code;

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class Demo {

    public static void main(String[] args) {

        try (var arena = Arena.ofConfined()) {
            Linker linker = Linker.nativeLinker();

            MethodHandle strcmp = linker.downcallHandle(
                    linker.defaultLookup().find("strcmp").orElseThrow(),
                    FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS)
            );

            MemorySegment string1 = arena.allocateFrom("String");
            MemorySegment string2 = arena.allocateFrom("String");

            int invoke = (int) strcmp.invoke(string1, string2);

            System.out.println(invoke);

        } catch (Throwable e) {
            throw new RuntimeException(e);
        }


    }
}

Using your own C library

If you want to use your library there is the change ‘linker.defaultLookup()’ won't be able to find the C method. In those cases, you need to load it yourself. You can do that using the system::loadLibrary method or by creating a SymbolLookup. Creating a SymbolLookup has the benefit that the library will be loaded as long as the arena it's connected to is active. This means that when the arena gets closed the library will be unloaded and free up resources.

You create a symbolLookup like so:

1
SymbolLookup symbolLookup = SymbolLookup.libraryLookup("path/to/your/library", arena);

This loads the library and binds the lifetime to that of the arena.

The next thing you want to do is use that symbolLookup when creating a MethodHandle. When using the downcallHandle pass the symbolLookup as a parameter. The linker will now look inside the C library to find the method you want to call.

1
2
3
MethodHandle c_method_name = linker.downcallHandle(
        symbolLookup.find("c_method_name").orElseThrow(),
        descriptor);

This is all you need to load a library that is not loaded by default, or not used by the linker.defaultLookup().

Conclusion

In this post, we looked at how to make a call down to a C library. This is done by using the foreign function and memory API. This is an alternative way of calling C methods instead of having to use JNI.