Skip to main content
Blog

Native code interop with JNA

By 29 mei 2015januari 30th, 2017No Comments

In enterprise situations you will often find a mashup of polyglot systems, many systems and many languages working together. The glue that commonly binds different systems are databases. You can imagine though, that going through the database, using a network connection, is not always always suitable. In many cases a simple local in-process call offers the necessary performance gains.

For interaction with native code there are many libraries and frameworks that offer help. The default way for Java would be JNI, which is targeted to C/C++ and just not that simple to use. Mapping different data structures is the main problem.
Going from Java through C to Fortran, the target language in our case, actually requires twice the memory type mapping and conversion code meaning double the pain. Using JNA instead (skipping the C in-between code), is relatively easy and simple, as you will see.

In this example Fortran will be used as the target language that produces the native (compiled) library to interact with. Note that any language could have produced the actual native library, though things like method signatures and data formats will vary wildly! Even when staying with one language, different compilers, compiler options, platforms, etc. can have dramatic effects on the nitty-gritty details that make an actual native call either work or fail.

Requirements:

  • Java JDK, installed
  • Groovy, installed and on the path
  • gfortran, install using a package manager

Fortran (hello.f95)
An example piece of Fortran code (disclaimer: I’m not a Fortran programmer ;D).
It prints the two inputs and returns a simple String through an output parameter (yep, that’s not even possible in Java, but common in Fortran).

subroutine HELLO(myInput, anotherThing, wordOfThanks)
  implicit none
  character (len=15), intent(in) :: myInput, anotherThing ! input
  character (len=15), intent(out) :: wordOfThanks ! output
  print*,"Hello world, from fortran!"
  print*,"myInput: ",myInput
  print*,"anotherThing: ",anotherThing
  wordOfThanks = "from fortran!  " ! mind the length
end subroutine HELLO

The groovy shell script (libgreet.groovy.sh)
The shell script calls the native library. It uses the Grape system to grab the JNA library from the public maven repositories. To be able to call a native piece of code, you will need to define the interface you expect. This is not actually checked when compiling, the call will be made dynamically in runtime and fail when it turns out to be incorrect.

#!/usr/bin/env groovy
 
@Grab( 'net.java.dev.jna:jna:3.4.0' )
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Memory
import com.sun.jna.Pointer

// (g)fortran somehow compiles routine names with an underscore postfix
interface Greeter extends Library {
  public String hello_(String first, String second, Pointer result)
}
 
// we need to tell JNA where and how to load the native library
System.setProperty("jna.library.path","./")
Greeter test = Native.loadLibrary( 'libgreet.so', Greeter )
 
Pointer resultPointer = new Memory(Pointer.SIZE*15)
 
// Call the fortran routine (mind the string lengths)
test.hello_("testing first  ", "testing second ", resultPointer)
 
println "result: ${resultPointer.getString(0)}"

Hey it’s not C
Reading the output parameter requires JNA Pointer magic which kinda resembles the way it works in C(++). The fun thing about Fortran is that is handles strings in the same way as java does (in memory), which actually makes interop with java easier than with C(++).

Compiling
To compile the example (on linux, or just use a virtual machine to run linux: https://www.vagrantup.com)

#!/bin/bash
rm *.log *.o libgreet.so
gfortran -c hello.f95 -fPIC
gfortran -shared -o libgreet.so hello.o -nostartfiles

You can now run the groovy script by calling it like any regular shell script:
(just remember to make it executable: chmod u+x)

./libgreet.groovy.sh

Mind the memory
Voila. Now if you do many calls or have very long running processes that require interop, you may need to investigate memory management. JNA has facilities to free memory by hand, which may or may not be required for interop with your target native library.

More information on JNA can be found here.