GeneralBrokenLines V03-01-03
using EIGEN
JNA Usage

how to use wrapper functions within java

More information about JNA can be found at its GitHub repository.

In general, the most difficult part of implementing the JNA-GBL interaction is getting the transfer of data between the java objects and the C++/GBL objects done well and safely. The JNA documentation apart of its repository contains more information about how to do this well - here I will simply walk through an example accessing a single class from GBL within java. In reality, a working solution would require a similar setup for almost all of the different GBL classes one would be interested in using.

It is suggested to enable the JNA_DEBUG build option when first developing a JNA-based java usage of GBL. This will help you make sure that you aren't leaking memory and avoid a program crash.

Wrapping MilleBinary

It is easiest to simply look at the java source code for this class so one can see how to interface with it. The basic idea is to define a singleton that represents the GBL library as loaded by JNA and then have classes whose job is to wrap these functions in simpler, object-oriented forms.

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
// extension of JNA Library that represents the GBL dynamic library
public interface GBLInterface extends Library {
GBLInterface INSTANCE = (GBLInterface) Native.loadLibrary("GBL", GBLInterface.class);
Pointer MilleBinaryCtor(String filename, int filenamesize, int doublePrec, int keepZeros, int aSize);
void MilleBinary_close(Pointer self);
// would add more functions whose signatures "match" the ones defined in this file
// (more on what "match" means below)
}
// java class that represents a MilleBinary file
public class MilleBinary {
// hold onto the object dynamically allocated from C++
private Pointer self;
// provide a constructor that is easier for a user than the raw C-wrapped function
// make parameters easier, for example doing the boolean->integer conversion for the user
public MilleBinary(String fileName, boolean doublePrec, boolean keepZeros, int aSize) {
self = GBLInterface.INSTANCE.MilleBinaryCtor(fileName, fileName.length(),
doubePrec ? 0 : 1, keepZeros ? 0 : 1, aSize);
}
// dynamically allocated memory done by the native library is not cleaned up
// by the JVM so one must manually clean it up
public void close() {
GBLInterface.INSTANCE.MilleBinary_close(self);
}
};
void MilleBinary_close(MilleBinary *self)
Closing a gbl::MilleBinary file is the same as destructing it.
MilleBinary * MilleBinaryCtor(const char *fileName, int filenamesize, int doublePrecision, int keepZeros, int aSize)
Dynamically create new MilleBinary file.

Matching Function Signatures

For function signatures to "match" between the functions defined in the JNA library extensions (GBLInterface above) and the ones defined here, the return value type, the name, and the argument types need to match. The name is easy, but the types are slightly more complicated since the typename between java and C++ are different.

  • The simple types (int and double) are the same.
  • A java String is converted to a C-style string char *.
  • A pointer to any structure in C is represented by Pointer on the JNA side.
  • If the C side needs a variable passed by reference, one needs to use IntByReference (or DoubleByReference) on the java side and a pointer on the C side.
  • If an array is of a known length, one can allocate the array in java and then simply pass the pointer to the array to the C side.
    • e.g. A length-3 array is common to represent position. Both "sides" could use the double position[3] syntax and one just has to make sure to allocate the correct size on the java side and the C function will simply write to those addresses.
  • If the array length must be determined by the C side, then one must use PointerByReference.

Memory Handling

When using JNA to call functions from a native library, the memory allocated by those functions is not monitored and cleaned up by java's garbage collector. Effectively, this means you need to have a delete call for every Ctor call you make.

Since this can get complicated very quickly, it is recommended to develop your java program with JNA_MEMORY_MONITOR enabled in the GBL C++ library. This will print out a summary of the GBL structures still allocated at the end of running allowing you to make sure that the GBL structures you created are also deleted.

cmake -B build -S . -DJNA_MEMORY_MONITOR=ON