Contents

Upcall Java code from C code

Written by: David Vlijmincx

Introduction

In the post, we will register a Java method as the callback method for the signal method that is defined in the standard C library. The goal is to register a callback which is a Java function and make the Java application print a message to the console whenever the application receives a signal. In other words, a C method will make an upcall to the Java application each time a signal is received.

Upcall

With an upcall we want the C code to call a Java method. To do that we need to pass a reference of that method to a C method. In this case, we want to pass a method reference to the signal method. To create the right method, you need to know what the signature of the method is. In the case of the Signal method it looks like the following:

1
void (*signal(int sig, void (*func)(int)))(int)

The Signal method takes an int value and a function that returns a void and has an int as a parameter. So I need to create a method and a reference to this method that match this signature.

The first step is to create a method that matches the required signature of the second parameter of the signal method. It could look like this:

1
2
3
static void handleSignal(int signal) {
        System.out.println("Received signal: " + signal);
}

The previous method returns a void and takes an int as a parameter. The name of the method or parameter does not matter. The important part is that it matches the signature that is defined in the Signal method.

The first step to create a reference to handleSignal is to create a method handle for it.

1
2
3
4
MethodHandle handleSignal = MethodHandles.lookup()
                .findStatic(UpCallExample.class,
                        "handleSignal",
                        MethodType.methodType(void.class, int.class));

Using MethodHandles.lookup() I can search for methods inside my application to create method handles. I use findStatic to find a static method that matches the following criteria:

  • Located in the UpCallExample class
  • Is named handleSignal
  • Returns a void and takes an int as a parameter.

MethodType.methodType(void.class, int.class));

Can look a bit confusing, the first value is the return type of the method you are looking for. Every value after that are parameters of the method you are looking for.

The next step is to create a FunctionDescriptor. The function descriptor is another definition of the method signature that you need to define.

For the handleSignal method, it looks like the following:

1
FunctionDescriptor signalDescriptor = FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT);

I use the FunctionDescriptor.ofVoid method to create a FunctionDescriptor. The handleSignal method returns a void, so I also want to create a FunctionDescriptor that returns a void using the ofVoid method.

The next step is to create an upcall stub. This creates a stub of the method that is safe to pass to a C method. To create it you need the MethodHandle, and FunctionDescriptor that were created earlier. The arena can be of type auto, in this case, the garbage collector will take care of deallocating the memory.

1
2
3
MemorySegment handlerFunc = linker.upcallStub(handleSignal,
                signalDescriptor,
                Arena.ofAuto());

This is all you need to create the upcall. The next step is to pass this stub to the signal method.

Down call

For the C code to call the Java method, you need to pass the stub to the Signal method first. To call the signal method we need to create a downcall using a Linker. Using the linker you can use the defaultLookup() to find the Signal method.

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

Same as with the upcall, the FunctionDescriptor describes the signature of the signal method. In this case, The Signal method returns an int and takes an ADDRESS as a parameter. The address is the memory segment of the upcall stub, that was created in the previous section.

All that is left to do is call the signal method with the reference to the Java method.

1
 signal.invoke(2, handlerFunc);

After calling invoke every signal that is equal to 2 will trigger the callback function. One way to do this is to use the kill command like so: kill -2 PID. To execute this command you have to know what the PID is of your application.

In a terminal you execute kill -2 $your_PID a few times while running the application you will get the following output:

1
2
3
4
Received signal: 2
Received signal: 2
Received signal: 2
Received signal: 2

Each time a signal is received a line will be printed to the console.

Complete code

This is the complete code used for this post.

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.davidvlijmincx;

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.time.Duration;

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

// kill -2 PID
public class UpCallExample {

    public static void main(String[] args) throws Throwable {
        printPID();

        final var linker = Linker.nativeLinker();

        // up call
        // locate the method that will be called from the C code
        MethodHandle handleSignal = MethodHandles.lookup()
                .findStatic(UpCallExample.class,
                        "handleSignal",
                        MethodType.methodType(void.class, int.class));
        
        FunctionDescriptor signalDescriptor = FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT);
        
        // Create a stub of the method to get an address in the form of a memory segment
        MemorySegment handlerFunc = linker.upcallStub(handleSignal,
                signalDescriptor,
                Arena.ofAuto());

        try (var _ = Arena.ofConfined()) {

            // Down call -  void (*signal(int sig, void (*func)(int)))(int)
            // Pass the stub to the signal method
            MethodHandle signal = linker.downcallHandle(
                    linker.defaultLookup().find("signal").orElseThrow(),
                    FunctionDescriptor.ofVoid(JAVA_INT, ADDRESS)
            );

            signal.invoke(2, handlerFunc);

            for (int i = 0; i < 60; i++) {
                Thread.sleep(Duration.ofSeconds(1));
            }

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

    }

    private static void printPID() {
        long pid = ProcessHandle.current().pid();
        System.out.println("pid = " + pid);
    }

    static void handleSignal(int signal) {
        System.out.println("Received signal: " + signal);
    }
    
}

Conclusion

Using the new features of project Panama it is possible to have callbacks from C methods. All you need to do is create an upcall stub and pass it using a down call to a C method that can use it.