Tuesday, 11 June 2019
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.
#Java has changed more in the last year than in the previous five years.
— GOTO (@GOTOcon) June 5, 2019
Learn about these changes and what Java's future holds in this #GOTOchgo talk with @gsaab and @MikaelVidstedthttps://t.co/QcX8MnEQZL
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:
This allows you to concentrate on writing the core logic of your application, rather than fiddling with glue code and integration details.
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.
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:
$JAVA_HOME/bin/jextract -t org.unix -L /usr/lib -lcurl \
--record-library-path /usr/include/curl/curl.h \
-o curl.jar
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.
import java.lang.invoke.*;
import java.foreign.*;
import java.foreign.memory.*;
import org.unix.curl.*;
import org.unix.curl_h;
import static org.unix.curl_h.*;
import static org.unix.easy_h.*;
public class Main {
public static void main(String[] args) {
try (Scope s = curl_h.scope().fork()) {
curl_global_init(CURL_GLOBAL_DEFAULT);
Pointer<Void> curl = curl_easy_init();
if(!curl.isNull()) {
Pointer<Byte> url = s.allocateCString(args[0]);
curl_easy_setopt(curl, CURLOPT_URL, url);
int res = curl_easy_perform(curl);
if (res != CURLE_OK) {
System.err.println("Error fetching from: " + args[0] + " ERR: " + Pointer.toString(curl_easy_strerror(res)));
curl_easy_cleanup(curl);
}
}
curl_global_cleanup();
}
}
}
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?
Here’s a more complete example application that utilizes libSSH2 [4] to implement a simple SSH client.
If you’d like to have a go and adjust it to run on Windows, I’ll greatly appreciate a PR.
A few points from my side that I learned or have been thinking about while working with Panama.
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.Void
argument types. The Foreign Docs [3] have an example about this case when using the TensorFlow C API.Thanks for reading!🍻