Intland's free requirements, development and test management hosting.
This server hosts 100.000+ users on the cloud!
Tags: 

FORTRAN 90/95 and Java interoperability using JNA

This section will introduce the use of JNA (Java Native Architecture) for direct Java/FORTRAN interoperability.

JNA has been originally developed to allow for easy Java and C/C++ communication. It does not burden the developer with JNI management and other intermediate files/APIs to In contrast to JNI wich supports static interoperability JNA uses dynamic dispatching at runtime to connect to native DLL directely from Java. JNA's design aims to provide native access in a natural way with a minimum of effort. No boilerplate or generated code is required. While some attention is paid to performance, correctness and ease of use take priority.

Examples for C/C++ are available, however the use of FORTRAN within the scientific community is as much as important. It can be achieved with the 'out-of-the-box' JNA library. The objective of this section is to show how to craft, compile, and link FORTRAN code to be accessible directly from Java using JNA.

The following sections show different examples of JNA/FORTRAN interoperability. For general information about JNA and C/C++ examples please look at the JNA website

Calling a FORTRAN Function/Subroutine with scalar arguments by value from Java.

The following example function takes two arguments and returns their product.

  • It uses the BIND keyword to provide for a C name binding. In Java/JNA this function can be called under that name.
  • The function parameter are declared as value parameter. If omitted, a and b would be called by reference.
FUNCTION mult(a, b) BIND(C, name='foomult')
    INTEGER,VALUE :: a,b
    INTEGER :: mult

    mult = a * b
END FUNCTION

The FORTRAN function above can be referenced and fetched using JNA:

interface F95Test extends com.sun.jna.Library {
    F95Test lib = (F95Test) Native.loadLibrary("F90Dyn", F95Test.class);
        
    int foomult(int a, int b);
}

  • The FORTRAN function resides in file libF90.dll, that is accessible in the jna.library.path.
  • The static call Native.loadLibrary belongs to the JNA API and binds all interface methods as specified in F95Test to their counterparts in libF90.dll.
  • The Java function uses the BIND name. This solves naming problems that results from different handling of names in object files/dlls with respect to underscoring. Using BIND}] is highly recommended, since it ensures a consistent external name for the function/subroutine regardless of the compiler being used and its location within a module.
  • Since function arguments ate passed in by value, regular native int types can be used within the Java method prototype. However assigning new values to a and b won't be propagated to the caller.
The method can now be called like this:
...
int result = F95Test.lib.foomult(20, 20);
assert result ==  400;
...
For more details on compilation/linking see further below.

Calling a Function/Subroutine with scalar arguments by reference.

To call a subroutine with arguments by reference, you shall not use the VALUE keyword on FORTRAN argument declaration. Now you can assign new values to the arguments, that will be later visible to Java.
SUBROUTINE ffunc(a, b) BIND(C,"reffunc")
    INTEGER :: a,b
    a = 3
    b = 5
END SUBROUTINE
The Java interface method needs to be modified to support call by reference via the JNA API ByReference classes.
...
void reffunc(ByReference a, ByReference b);
...

The reffunc subroutine will be called as follows:

...
IntByReference a = new IntByReference(0);
IntByReference b = new IntByReference(0);
F95Test.lib.reffunc(a, b);
assertEquals(3, a.getValue());
assertEquals(5, b.getValue());
...

Now you create the int reference objects, pass them into reffunc and retrieve the values with .getValue().

Array Arguments

Single and Multidimensional arrays can be handled in JNA/Java and FORTRAN. Like with Strings, the length of the array has to be passed in with additional arguments.

foo.f95:'

SUBROUTINE inc(arr, len) BIND(C, name='fooinc')
    INTEGER,DIMENSION(len) :: arr
    INTEGER,VALUE :: len
    INTEGER :: i

    DO i = 1, len
        arr(i) = arr(i) + 30
    END DO
END SUBROUTINE

SUBROUTINE arr2d(arr, m, n) BIND(C, name='arr2d')
    INTEGER,DIMENSION(m,n) :: arr
    INTEGER,VALUE :: m
    INTEGER,VALUE :: n
    INTEGER :: i,j

    DO i = 1, m
        DO j = 1, n
            arr(i,j) = arr(i,j) + 1
        END DO
    END DO
END SUBROUTINE

The examples above show the declaration and the use of a one and two dimensional array as subroutine arguments. The array is dimensioned by the extra parameter, they are passed in as value arguments.

The JNA/Java declaration part is shown below. Note that the multidimensional array, has to be one-dimensional in Java. FORTRAN will lay it out correctly by using the dimension lengths that are passed in.

interface F95Test extends Library {
  ...
  void fooinc(int[] arr, int len);
  void arr2d(int[] arr, int m, int n);
  ...
}

The use if the one dimensional array is pretty simple. The other example required a bit management on the java side, that is not shown here.

//1D
int[] a = {1, 2, 3, 4, 5};
lib.fooinc(a, a.length);
assertArrayEquals(new int[]{31, 32, 33, 34, 35}, a);

//2D
int[] a = {1, 2, 3, 4, 5, 6};
lib.arr2d(a, 3, 2);
assertArrayEquals(new int[]{2, 3, 4, 5, 6, 7}, a);

If a real Java multidimensional array needs to used in FORTRAN, it needs to be flattened into 1D, or you use an access method in Java to use a 1D Array in a 2D way. Not pretty but it works!

String Arguments

String arguments are always special, since Strings are represented differently in almost all languages. In FORTRAN, you declare a string argument as follows, note that the size of the string has to be passed in as an additional argument.

The following function takes a string argument and verifies the content and length. The argument line is defined as a CHARACTER array, its length is passed as a second argument by value, and it is being used to dimension the length of the string.

FUNCTION strpass(line, b) BIND(C, name='foostr')
    CHARACTER(len=b) :: line
    INTEGER, VALUE :: b
    LOGICAL :: strpass

    strpass = (line == 'str_test') .AND. (b == 8)
END FUNCTION

The Java/JNA prototype looks like this:

 ...
 boolean foostr(String s, int len);
 ...

The application will need to pass in the string and obtain the actual string length.

...
String test = "str_test";
boolean result = lib.foostr(test, test.length());
assertTrue(result);
...

Modules

Modules can be used to place all subroutines/functions that should be used via JNA, its good practice. A module allows for global data, an module level IMPLICIT NONE. Again, it is recommended to use the BIND keyword since the compiler might alter the subroutine name in the DLL otherwise, since it is a different scope.

MODULE test

 IMPLICIT NONE

 CONTAINS
    
 SUBROUTINE ffunc(a, b) BIND(C,"reffunc")
    INTEGER :: a,b
    a = 3 
    b = 5
 END SUBROUTINE
 
END MODULE test

The example above the subroutine ffunc can still be called as reffunc from JNA/Java.

TYPE Arguments

Type arguments for functions can be handled too. This allows the passing of complex objects directly from Java to FORTRAN. Lets suppose you have the following FORTRAN code, that defines a TYPE for a City and a subroutine typepass that takes such an argument.

MODULE test

 IMPLICIT NONE

 TYPE :: City
    INTEGER  :: Population
    REAL(8)  :: Latitude, Longitude
    INTEGER  :: Elevation              
 END TYPE

 CONTAINS

 SUBROUTINE typepass(c) BIND(C, name='footype')
    TYPE(CITY) :: c

    c%Population = c%Population + 1000
    c%Latitude = c%Latitude + 5
    c%Longitude = c%Longitude + 5
    c%Elevation = c%Elevation + 9
 END SUBROUTINE

END MODULE test

Both the TYPE and the subroutine are placed in a module.

Now lets look at the JNA/Java counterpart that defines the interface for typepass:

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;

public static class City extends Structure {

   public int Population;
   public double Latitude,  Longitude;
   public int Elevation;
}

interface F95Test extends Library {

   void footype(City c);
}

There is an Java class called City that must have the identical internal layout to its FORTRAN TYPE. The names, however, do not matter. It also has to be subclass of Structure which is defined in the JNA API.

Note that all fields of City have to be public to allow JNA to compute its size. The F95Test method again used the BIND name and the City argument.

An application will instantiate theCity object and pass it in as usual.

   ...
   City city = new City(3000, 0.222, 0.333, 1001);
   F95Test.lib.footype(city);

   assertEquals(4000, city.Population);
   assertEquals(5.222, city.Latitude, 0.0001);
   assertEquals(5.333, city.Longitude, 0.0001);
   assertEquals(1010, city.Elevation);
   ...

Pitfalls and Obstacles

  • Always be aware that FORTRAN subroutine/function arguments are passed by reference, unless the VALUE modifier is used. You might end up accessing memory that might cause a segfault. Therefor use always Native.setProtected(true) to provide for more memory protection in the JNA site, if supported for your architecture.
  • If JNA cannot find your function in a DLL and both names match in source, do not panic. You should explore the DLL to find out the real name in your DLL, since this is what JNA is looking at not the source. Do something like nm libF90Test.dll | grep reffunc if reffunc the the function you'd like to call. You'll see maybe a different (more underscores in the name, or a module name prefix) name depending on the compiler and compiler flasg. This is the name you should use in your Java interface. To make this more transparent use the BIND keyword in your source to ensure the proper name in the DLL.
  • If you pass Java objects to FORTRAN as TYPE, all Java fields have to be public. JNA will complain at runtime not being able to determine the size of the java object.

  • Be aware of the array ordering in FORTRAN that sees a two dimensional array always in COLUMN/ROW order. Also, you cannot pass a real multidimensional Java array to FORTRAN, since those do not have a continuous memory layout. On the Java side you always have to manage a one dimensional array that you reshape for FORTRAN by passing in its dimensions.
  • If a DLL cannot be found at runtime, you need to set the search path. You can set the system property jna.library.path to point to paths on your filesystem. You also use the NativeLibrary.addSearchPath method to add a map a directory to a specific DLL name.

Data type mapping

The following table shows equivalent data types between FORTRAN and Java, ehen passed by value

Fortran JNA/Java
INTEGER(Kind=8) int
INTEGER(Kind=4) short
REAL(Kind=4) float
REAL(Kind=8) double
LOGICAL boolean
Character byte
CHARACTER(len= ) String

Setting up your Java project

You can use any IDE to develop your JNA supported Java code, as long as you make the file jna.jar a part of your classpath. This is the only library you need! See the References section for download.

Dynamic Library Generation

The following sections will provide some help for managing the build process using different compilers. GNU's compiler collection and the G95 spin-off, as well as the Intel Compiler suite seem to be the most important tools for the general developer.

G95

G95 allows compiling and linking into a DLL. Note that G95 is not a part of the GNU compiler collection. To compile and link a FORTRAN source into a DLL use the following flags for GCC tools:

Compile a FORTRAN source into an object file:

g95 -fno-underscoring  -c -g -o build/ftest.o ftest.f90

Link the DLL:

g95 -Wl,--add-stdcall-alias -shared -o dist/libF90Dyn.dll build/ftest.o  

Note that you have to use G95 for linking too. This ensures for linking the right FORTRAN runtime libraries into your DLL

GNU GFortran

GFortran allows compiling Fortran code into a shared library (.so on Linux) for use with JNA:

Compile a FORTRAN source into an object file:

gfortran -fno-underscoring -fPIC -c -g -o build/ftest.o ftest.f90 

Link the .so:

gfortran -shared -o dist/libftest.so ftest.o 

Note that you have to use GFortran for linking too. This ensures for linking the right FORTRAN runtime libraries into your shared object.

NOTE: These instructions have been tested on Ubuntu Natty only.

Intel ifc

TBD