Last week I watched a pretty cool GOTO Conference talk [1] with Mikael Vidstedt, director of Software Engineering at Oracle, where he presents many of the upcoming and currently being worked on Java features. One of those, Project Panama, stood out as particularly interesting to me.
Disclaimer: Project Panama is still in its early stages of development, so the contents of this article may not reflect its current state.
Project Panama, available in early-access JDK 13 builds, is meant as a bridge between Java and native code. How is this useful? There are certain use cases where a piece of functionality is available as a library written in C or C++ and the requirement is to integrate it into a Java application. The current solution is to use the Java Native Interface (JNI) [2] which could require a vast amount of work and is also quite error-prone. You need to have a good grasp of the native library's insides and then write all the required implementations to bridge the Java interface with the native code. From my experience, calling native library functions that may change at some later point in time and also managing heap memory allocations could be a real challenge with JNI. To quote Mikael as he says it on the video:
How many people here have written JNI or, you know, done JNI? We're so sorry for you!
This is where Panama comes into play. It provides new tools and API to simplify the bridging between Java and native code. It basically boils down to the following steps:
Generate Java bindings (interfaces) from existing C header file(s) using the jextract tool.
Invoke C functions via the jextracted Java interface using the java.foreign API.
This allows you to concentrate on writing the core logic of your application, rather than fiddling with glue code and integration details.
Hands-on
Project Panama's documentation pages [3] already provide a solid number of examples to start with. I'll just take a quick peek at how to bridge and run a libCurl Java app and then I'd like to present a more detailed example - a simple SSH client that I wrote based on libSSH2.
I'm running these examples on a macOS, but with a few tweaks you should also be able to run them on a Linux installation.
The libCurl App
How to download a web page using the native Curl library's implementation? Well, first we need to get and extract a Panama OpenJDK build archive.
Let's open up a shell and set the JAVA_HOME environment variable to where the OpenJDK build archive is extracted.
export JAVA_HOME=/opt/jdk-13.jdk
Now we need to generate the Java interfaces, the glue code that will bind Java code to the native library. This will produce a curl.jar file:
When we inspect the JAR file, we can see all the Curl API calls, as well as dependency bindings. The Curl API is available through the new java.foreign Java API.
Now for a quick example. Here's a Java piece of code that fetches a web page and displays its contents on screen.
A couple of things to point out here. Notice how we cannot directly pass a Java String to the curl_easy_setopt() call. This call accepts a memory address pointer as the url parameter, so we first need to do a dynamic memory allocation operation using the Scope and pass a Pointer interface instance instead. As you may find in the Panama tech docs [5], the Pointer interface helps a lot when it comes to complex C-alike pointer operations like pointer arithmetic, casts, memory dereference, etc. The Scope manages the runtime life-cycle of dynamic allocated memory.
Alright, now armed with this knowledge, can you extend this code to write the contents of a Curl fetched web page to a file stream?
The libSSH2 App
Here's a more complete example application that utilizes libSSH2[4] to implement a simple SSH client.
NOTE: This is an experimental project. Stability is not guaranteed.
Install
The client has been tested on macOS and Linux. It should also be possible to run it on Windows, however, no work has been done in that direction. PRs are welcome!
If you'd like to have a go and adjust it to run on Windows, I'll greatly appreciate a PR.
Final Thoughts
A few points from my side that I learned or have been thinking about while working with Panama.
The Scope is pretty powerful. You can allocate callback pointers, structs, arrays. The concept of layouts and layout types deserves more time for me to fully explore and grasp.
It comes as no surprise that writing Java code using a native library is more extensive and requires extra care, especially when it comes to not forgetting to invoke cleanup API calls that the library requires.
I/O in most native libraries requires a file descriptor, which isn't easy to get in Java [6]. This, however, is not directly related to the java.foreign API.
Some libraries define C++ style function prototypes without argument as opposed to C-style Void argument types. The Foreign Docs [3] have an example about this case when using the TensorFlow C API.
I haven't explored if it would be possible to use Panama with Go or Rust created native libraries. That would be pretty cool.