Welcome to Part 4 of Java's Project Panama for newbies! If you've been following this series of posts on how to use Java's Project Panama APIs you probably have noticed that all of the examples thus far demonstrate how Java code can execute C code by using the linker
's downCall()
method.
But, did you know that you can go in the opposite direction (C code executes Java Code)?
Answer: YES!
Why is this capablity amazing, you ask? Well, imagine C code capable of performing a computation and after its completion the C code will notify Java code to perform updates to JavaFX UI components.
In this article you will learn about callbacks and how to create them in both Java and in C. In principle, you will learn how to create Java code that can be executed from C code using linker
's upcallStub()
method.
In addition to this article I will show you how to create a native library written in C that will be later accessed using Java's newer Foreign Access APIs (Project Panama JEP 419).
Note: This tutorial is now using JDK 19-ea (JEP 424) using the release of build 19-ea+25 (2022-09-20). If you aren't familiar with the newer API changes between JEP 412 & 419 you can check out my talk from the Fosdem2022 conference called Native Language Access: Project Panama for Newbies. JDK 17 uses JEP 412 while JDK 18+ uses JEP 419.
For the impatient please head over to GitHub at: https://github.com/carldea/panama4newbies/tree/jdk19-ea/part04
If you are new to this blog series please check out Part 1, Part 2, and Part 3. In Part 4 we will look at what are C native function pointers and later how to create a Java method to be passed into a function as a callback.
The article's agenda is as follows:
- Application Layers
- What's a Callback?
- What's a C Function Pointer?
- Creating a Native Shared Library
- Using the
jextract
tool to generate Java bindings - Creating a Java program to access a Native Library
- Run Java program
PanamaCallback.java
- Conclusion
Application Layers
Whenever developing applications with dependencies it's a good idea to view component layers at a high-level (Top-down approach). This is especially important when you are sharing native resources that could potentially be used by other applications and services on the same node(device). Shown below are the components stacked upon each other.
Above you'll notice a Java Application block that depends on the JVM and Native library blocks respectively. In addition to the top layer you'll notice a C application (block) that also depends on the native library (block) as a shared resource. The rest of the layers are the operating system and hardware.
Enough about top-down views of the application layers let's look at things from a bottom-up approach by learning C language concepts and then later create a native library that will be accessed by a Java application. Before getting into code let's talk about callbacks.
What is a Callback?
According to Wikipedia:
A callback, also known as a "call-after" function, is any reference to executable code that is passed as an argument to other code; that other code is expected to call back (execute) the code at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later point in time as in a asynchronous callback. Programming languages support callbacks in different ways, often implementing them with subroutines, lambda expressions, blocks, or function pointers.
In Java we can create callback behaviors by creating methods that can return or receive a lambda expression. Shown below is a synchronous callback where a method will do work pre and post invocation.
public static void myJavaCallback(Runnable codeBlock) { log.info("Begin calling codeBlock"); codeBlock.run(); log.info("Finished calling codeBlock"); } Runnable codeBlock = () -> System.out.println(" Inside codeBlock"); this.MyClass.myJavaCallback(codeBlock);
The output of the code snippet above:
Begin calling codeBlock Inside codeBlock Finished calling codeBlock
Asynchronous callbacks can be quite useful too especially when you want to defer code execution at a later time. To defer code execution you may explore Java's CompletableFuture
API.
So, now that you know how to create callbacks in Java you may be wonder how is this done in the C Language?
Answer: Function Pointers
What is a C Function Pointer?
According to Wikipedia :
A function pointer, also called a subroutine pointer or procedure pointer, is a pointer that points to a function. As opposed to referencing a data value, a function pointer points to executable code within memory. Dereferencing the function pointer yields the referenced function, which can be invoked and passed arguments just as in a normal function call.
As a Java developer you can think of a C function as a first class citizen as a static singleton object. Similar to a Java SAM (single abstract method) object stored as a named variable at some memory location (address). Another way to observe functions is that they are statically defined and synonymous to Java's public final static
methods. As we will see later, this will allows us to pass a function into another function and therefore enabling API developers to create callback behaviors.
Now that you know what a callback is and how it behaves in the C language, let's create a C native library containing C function callbacks that receive function pointers as a parameter.
Creating a Native Shared Library
Before getting into Java code let's look at a simple example of creating a native library in C. This library will be used later by our PanamaCallback.java
example. When creating a shared library you will need the following:
- C compiler - Most compilers will have options to generate a shared library such as GCC or Clang
- Header file (
mylib.h
)- Declares the functions to be implemented. - C file (
mylib.c
)- Implementation code.
The diagram below shows how to compile a C program into a native library for the MacOS platform. Use the the -o
switch to name your output binary for the respective OS. eg: Linux -o libmylib.so
, Windows -o mylib.dll
You'll notice the naming convention for the Mac OS where the name of the library is mylib
but the file name is libmylib.dylib
. This is good to know because in jextract
you will generate code that under the hood calls the System.loadLibrary("mylib");
. That way it will load the library in a similar way for all OS platforms. This will allow the library to be distributed with the application co-located with your Java code or specified in Java property java.library.path
.
To make things simple I used the C examples from the popular site Tutorials Point.com on C callbacks. I modified the examples slightly to show program flow between Java and C.
C header file mylib.h
Let's examine the mylib.h
file that consists of function declarations. These functions are exported or public to callers of the native library. Later, you will see the mylib.c
file that will implement the declared functions from the mylib.h
file.
The following are three functions signatures are declared.
Listing 1 mylib.h
- Functions defined
#include <stdio.h> void my_function(); void my_callback_function(void (*ptrToFunction)()); void my_callback_function2(void (*ptrToFunction)(int));
my_function()
- A regular C functionmy_callback_function()
- Receives a pointer to a function named ptrToFunctionmy_callback_function2()
- Receives a pointer to a function named ptrToFunction that also takes an int as a parameter.
Above you'll notice my_function()
as an ordinary C function that can be passed into the second function called my_callback_function()
. As you'll see later in the implementation code mylib.c
the main()
function will pass a function pointer referencing the C function my_function()
.
The last my_callback_function2() function is another example of a callback, but the function pointer to be passed in has a signiture of a void return type and one argument of type int
.
C implementation file mylib.c
Next, is the implementation file mylib.c
. The code will begin by including the mylib.h
as dependency. This is similar to Java interfaces. Shown in listing 2 is mylib.c file containing the function implementations of the functions define in mylib.h
file header.
Listing 2 mylib.c - Implementation code
#include <stdio.h> #include "mylib.h" void my_function() { printf("This is a normal function."); } void my_callback_function(void (*ptrToFunction)()) { printf("[C] Inside mylib's C function my_callback_function().\n"); printf("[C] Now invoking Java's callMePlease() static method.\n"); // Calling the passed in callback (*ptrToFunction)(); } void my_callback_function2(void (*ptrToFunction)(int)) { printf("[C] Inside mylib's C function my_callback_function2().\n"); printf("[C] Now invoking Java's doubleMe(int) static method.\n"); int x = 123; (*ptrToFunction)(x); //calling the callback function } int main() { printf("[C] Callbacks! \n"); void (*ptr)() = &my_function; my_callback_function(ptr); return 0; }
The first callback function my_callback_function()
receives a pointer to a function. The my_callback_function()
function will output two lines of text to the console and subsequently invoke the function that the function pointer ptrToFunction
is referenced at.
Above you'll notice the main()
function code that tests the my_callback_function()
function by assigning a function pointer named ptr
referencing the function my_function
as shown below:
void (*ptr)() = &my_function;
In C language the ampersand gets the address of the my_function()
function to be assigned to the variable ptr
. Notice the matching call signiture. e.g. returns void and void parameters (no parameters). Remember, the (*SOME_VARIABLE_NAME)
parenthesis and asterisk around the name denotes it is a variable of a pointer to a function.
Keep in mind the variable ptr
must follow the signiture of the function you want to assign. Later, in Java Panama code we will mimick a function pointer to be passed into the my_callback_function(void (*ptrToFunction)())
function.
With the header file and implementation file let's compile and create a native shared library.
Creating a Native Shared Library
Depending on the C compiler there are switches that will create a binary library. Most compilers will have the option -o
to allow you to name the library suitable for your operating system. In this scenario to support MacOS the naming would be prefixed with lib
and a file extension as .dylib
.
gcc -shared -o libmylib.dylib mylib.c
Afterwards the following is outputed as a native library local to the working path. Remember the name of the library is mylib
and the file naming below is specific to the MacOS.
libmylib.dylib
Later, you'll use the name mylib
as the library name for jextract
to be able to generate code that will load the library during runtime.
Java goes Native
After creating a native library in C let's use Project Panama's jextract
tool to generate classes and source code. Later, you will see how to access mylib
native library purely in Java code.
Using the jextract
tool
In Part 3 we mentioned that jextract
only allows you to target one header file at a time. To overcome this limitation, you can simply create a dummy header file such as foo.h
consists of multiple includes as shown below.
Listing 2 - foo.h
Containing multiple includes
#include <stdio.h> #include "mylib.h"
Above you'll notice headers in angle brackets vs double quotes. In short, for C standard hearders the code will use angle brackets and for third party libraries such as mylib will use the double quotes.
Let's go ahead and generate the Panama class files and source code using jextract
as shown below.
# Generate class files jextract --output classes \ -t org.unix \ -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \ -I . \ -l mylib \ foo.h # Generate Java source code jextract --source \ --output src \ -t org.unix \ -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \ -I . \ -l mylib \ foo.h
Above you'll notice the -I .
is to let jextract
know about the mylib.h
in the working directory. Also, you should notice -l mylib
as we mentioned earlier when the System.loadLibrary()
method is called to locate the library based on the java.library.path
property.
Now, that you've successfully generated binding code let's see how to reference these C functions manually as opposed to the convenience methods created by jextract
. Instead of using the generated code such as: foo_h.my_callback_function()
I will be showing you how to do things in a lower level way. This helps you understand what is going on under the hood.
Obtaining Native Symbols
This has been discussed in Part 3, to obtain C native functions or symbols. In Part 4 we are using JEP 424 updates to Panama's Foreign Function APIs. Instead of using the MemoryAccess
class from JEP 412, we now need to obtain native symbol objects from memory via the SymbolLookup
class (JEP 424) as shown below.
MemorySegment callback1 = SymbolLookup.loaderLookup().lookup("my_callback_function") .or(() -> Linker.nativeLinker().defaultLookup().lookup("my_callback_function")) .orElseThrow(() -> new RuntimeException("cant find symbol"));
After obtaining the symbol MemorySegment
let's create a method handle like the following:
var my_callback_functionMethodHandle = Linker.nativeLinker() .downcallHandle(callback1, FunctionDescriptor.ofVoid(C_POINTER));
After obtaining the symbol from memory (MemorySegment
) a downcallHandle()
method creates a MethodHandle
object for later invocation. Here, you have to describe the C function's call signature.
If you recall the C function my_callback_function()
call signature (In C) looks like the following:
void my_callback_function(void (*ptrToFunction)())
Here you'll notice the C function my_callback_function()
takes a function pointer as a parameter (C_POINTER
). Next, let's see how to pass in a Java static
method into the C native my_callback_function()
function. To keep things simple the callback will be a function that returns void and has void parameters. The void parameters just means empty arguments or no parameters.
Creating a Function Pointer with Java Code
To mimic a function pointer in Java as described above (returns void and no parameters) you can simply create a static method as shown below:
public static void callMePlease() { MemorySegment cString = implicitAllocator() .allocateUtf8String("[JAVA] Inside callMePlease() method - I'm being called from C.\n"); foo_h.printf(cString); }
Above, the code creates a C string using the allocateUtf8String()
to create a MemorySegment
that subsequently is passed into a C's printf()
function for text output to the console. Of course the code could have used Java's System.out.printf()
, however when mixing between C's printf()
and Java's printf()
can have unexpected results such as the order in which text lines appears. This is due to a buffered output stream that isn't flushed.
Because of C's standard input/output buffer is separate from Java's standard input/output buffer which ever is flushed first will be outputted first. Let's look at a quick example of what I mean.
What will be outputted from the example below?
MemorySegment cString = ...// "1 - C stdout\n" foo_h.printf(cString); System.out.printf("2 - Java Panama\n");
The output of the above code snippet is as follows:
2 - Java Panama 1 - C stdout
Here, you'll notice the Java printf()
text line is first. To ensure the C's buffer is flushed use C's fflush()
or fprintf()
function as shown below:
MemorySegment cString = ...// "1 - C stdout\n" printf(cString); fflush(__stdoutp$get()); System.out.printf("2 - Java Panama\n"); // or MemorySegment cString = ...// "1 - C stdout\n" fprintf(__stdoutp$get(), cString); System.out.printf("2 - Java Panama\n");
The output should look like the following:
1 - C stdout 2 - Java Panama
To get back on track with callbacks & function pointers let's look at how to pass a Java method into a C callback function as a function pointer.
Pass Callback into a C Function
Before passing the above callMePlease()
Java method you'll have to create a MethodHandle
as shown below:
// Create a method handle to the Java function as a callback MethodHandle onCallMePlease = MethodHandles .lookup() .findStatic(PanamaCallback.class, "callMePlease", MethodType.methodType(void.class));
Next, you need to create a NativeSymbol
object using the upcallStub()
method. The NativeSymbol
objects are reference by the symbol's address in memory. This allows the code to pass Java static methods as C function pointers.
// Create a stub as a native symbol to be passed into native function. // void (*ptr)() MemorySegment callMePleaseNativeSymbol = Linker.nativeLinker().upcallStub( onCallMePlease, FunctionDescriptor.ofVoid(), memorySession);
To demonstrate a call to the my_callback_functionMethodHandle()
C function the following code snippet calls the invokeExact()
method by passing in the callMePleaseNativeSymbol
(NativeSymbol
).
// Invoke C function receiving a callback // void my_callback_function(void (*ptr)()) my_callback_functionMethodHandle.invokeExact((Addressable) callMePleaseNativeSymbol);
Above you'll notice a cast to Addressable
, this is because the function accepts a C_POINTER
. As defined earlier the code specifies the method signature as shown below:
Linker.nativeLinker() .downcallHandle(callback1, FunctionDescriptor.ofVoid(C_POINTER));
Anytime you see a downcall method to create a method handle using a C_POINTER as a return or an argument the object passed in must be an Addressable
. If the NativeSymbol instance is not cast into a Addressable
you'll get an Exception during runtime as follows:
Exception in thread "main" java.lang.invoke.WrongMethodTypeException: expected (Addressable)void but found (NativeSymbol)void at java.base/java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:523) at java.base/java.lang.invoke.Invokers.checkExactType(Invokers.java:532) at PanamaCallback.main(PanamaCallback.java:74)
To see the full listing head over to GitHub at PanamaCallback.java. For extra credit take a look at the my_callback_function2 to see a method signiture that receives a C int value.
Now that the code is able to talk to the native library let's compile and run the example (PanamaCallback.java
).
Running the Example
To compile and run do the following:
javac -d classes \ -cp classes:. \ --enable-preview --source 19 \ src/PanamaCallback.java
java -cp classes \ --enable-native-access=ALL-UNNAMED \ --enable-preview \ PanamaCallback
The output:
[Java] Callbacks! Panama style [C] Inside mylib's C function my_callback_function(). [C] Now invoking Java's callMePlease() static method. [JAVA] Inside callMePlease() method - I'm being called from C. [C] Inside mylib's C function my_callback_function2(). [C] Now invoking Java's doubleMe(int) static method. [JAVA] Inside doubleMe() method, 123 times 2 = 246.
The output above shows the code execution path by displaying [Java]
or [C]
prefixed each line to denote code being run inside the Java world or in the native C world (library).
There you have it, Java Panama and C callbacks for newbies!
Conclusion
You've now had a chance to learn about C function pointers and how they relate to callback behaviors in the C language.
Next, I showed you how to create a native shared library on the MacOS. This native library will be later used in Java Panama code.
After creating a native library, we used the jextract
tool to generate classes and source code.
Lastly, you learned how to obtain native symbols and create a Java method as function pointer to be passed into a native function to be invoked as a callback.
As always, comments and feedback are always welcome.
NoName,
Hopefully, the following is what you are looking for. For starters you’ll create a C function my_callback_function3() that will receive a function pointer (pointer to a Java static method) and returns an int.
As a test create struct Point with x, y, and have jextract generate binding code.
In C library mylib.h and mylib.c will have the following
[C Land]
mylib.h
struct Point {
int x;
int y;
};
void my_callback_function3(int (*ptrToFunction)(struct Point));
mylib.c
void my_callback_function3(int (*ptrToFunction)(struct Point)) {
struct Point pt;
pt.x = 5;
pt.y = 4;
int z = (*ptrToFunction)(pt);
printf("z = %d\n", z);
}
[JAVA Land]
In Java Panama Code do the following Steps:
Step 1: Create a static method to receive a struct (MemorySegment) and returns an int.
public static int addCoordinates(MemorySegment ptStruct) {
try (var memorySession= MemorySession.openConfined()) {
int x = Point.x$get(ptStruct);
int y = Point.y$get(ptStruct);
printf(memorySession.allocateUtf8String(">>> pt.x + pt.y = %d \n"), (x+y));
return (x+y);
}
}
Step 2: Create a MethodHandle mapped to the static method.
MethodHandle addCoordinatesMH = MethodHandles.lookup()
.findStatic(PanamaCallback.class,
"addCoordinates",
MethodType.methodType(int.class, MemorySegment.class));
Step 3: Create or convert to a C function pointer using the linker’s upcallstub().
MemorySegment addCoordinatesNativeSymbol = Linker.nativeLinker().upcallStub(
addCoordinatesMH,
FunctionDescriptor.of(C_INT, Point.$LAYOUT()),
memorySession);
Step 4: Invoke the native callback function passing in the converted function pointer.
my_callback_function3(addCoordinatesNativeSymbol);
Hope that makes sense,
Carl
Thanks for the answer Carl. It helps a lot. However, I have another question.
Sometimes C header file can contain only struct declaration while its implementation is in c file. For example,
struct mg_connection; /* 3rd party lib, not in my control */
I can perfectly well use it in my c file to define a handler function (as it is required by 3rd party API) as
static int AHandler(struct mg_connection *conn, void *cbdata) { mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: " "close\r\n\r\n"); //some implementation return 1; }
However, when I try to load mg_connection via Linker
MemorySegment mg_connection = SymbolLookup.loaderLookup().lookup("mg_connection") .or(() -> Linker.nativeLinker().defaultLookup().lookup("mg_connection")) .orElseThrow(() -> new RuntimeException("cant find symbol"));
I got exception – “cant find symbol”, jextract aslo does generate hepler classes for it (if I am not mistaken).
Is there a way to pass from Java to C a callback function, so that C can “see” at it as to a function with this signature
int AHandler(struct mg_connection *conn, void *cbdata)
.
and the next question (too many questions, right?) – How to deal with “void *cbdata”, if i know that it actually String type of data?
if I try to write a java callback that uses simple/small structs (like 2 member variables) – all is good. However, I did a test and created a struct with almost 200 ints parameters – java process crush on a start with memory dump written to a file.
Another interesting (I guess) behavior if I call my function with a 3rd party quite complex struct – I got this exception:
Exception in thread "main" java.lang.IllegalArgumentException: bad parameter count 298
at java.base/java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:169)
at java.base/java.lang.invoke.MethodType.checkSlotCount(MethodType.java:224)
at java.base/java.lang.invoke.MethodType.checkPtypes(MethodType.java:214)
at java.base/java.lang.invoke.MethodType.makeImpl(MethodType.java:405)
at java.base/java.lang.invoke.MethodType.methodType(MethodType.java:374)
at java.base/java.lang.invoke.MethodType.methodType(MethodType.java:244)
at java.base/jdk.internal.foreign.abi.CallingSequenceBuilder.computeTypeHelper(CallingSequenceBuilder.java:148)
at java.base/jdk.internal.foreign.abi.CallingSequenceBuilder.computeCallerTypeForUpcall(CallingSequenceBuilder.java:126)
at java.base/jdk.internal.foreign.abi.CallingSequenceBuilder.build(CallingSequenceBuilder.java:118)
at java.base/jdk.internal.foreign.abi.x64.sysv.CallArranger.getBindings(CallArranger.java:118)
at java.base/jdk.internal.foreign.abi.x64.sysv.CallArranger.arrangeUpcall(CallArranger.java:135)
at java.base/jdk.internal.foreign.abi.x64.sysv.SysVx64Linker.arrangeUpcall(SysVx64Linker.java:59)
at java.base/jdk.internal.foreign.abi.AbstractLinker.upcallStub(AbstractLinker.java:70)
at test.Application.myMgRequestInfo(Application.java:62)
Maybe those 2 exceptions are related or maybe I am doing smth wrong… but “bad parameter count 298” means that there are too many lambdas or a generated method with more than 255 parameters (JMM limit?)
How does it know which native library to call when you run it on different platforms like Linux, Mac, and Windows? Java is multi-platform.
Just like using JNI, the native library is installed or placed in default areas(lib directories) on the PATH.
Each operating system will have its respective library with a naming convention to be loaded using
System.load(). It will use the simple name. E.g.
libmylib.so,
libmylib.dylib,
mylib.dll
“mylib” would be the library name when loaded.
I hope that answers your question.
Carl
Great article! I got how to call the C function from java
void my_callback_function2(void (*ptrToFunction)(int)); //works!
However, I cannot figure out how to pass a C struct as an input parameter or return a struct for a C function
Let’s assume a have the C struct
struct item {
int id;
int code;
};
I need to initialize in Java and pass it to a C as the input parameter of the callback function smth like:
struct item my_test(int (*ptrToFunction)(struct item));
how to do that?