Calling FORTRAN and C from Java =============================== Much scientific programming is still performed in FORTRAN, and most of the remainder is written in C or C++, which are also used for many business and commercial programs. These programs have the advantage of being compiled into the native machine code used by the processor, but have the disadvantage that they are dependent on the platform. This is particularly the case if a graphical user interface (GUI) is used. Code written in Java, including GUIs, has the advantage of being, at least in principle, platform independent, thus can run on Windows, Linux, Mac and many other systems, and a Java compiler has the additional advantage of being downloadable from Sun's website at no charge. As Java is not compiled to native code, it has the disadvantage of being slower than FORTRAN or C/C++ if maximum speed is required. This document shows how we can get the best of both worlds by calling FORTRAN and C from Java using the Java Native Interface (JNI), moreover, by using a GUI in Java avoids having to convert existing FORTRAN or C/C++ programs into Java. The specific situation discussed here concerns FORTRAN 77, C and Java on a Linux Red Hat 8.0 platform using a simple example. On other platforms such as Windows, the principles should be similar, although the details of linking the codes will be different. The JNI can be used to link Java and C, we also show how to link C and FORTRAN, but as far as is known, there is no means of directly linking Java and FORTRAN, so if this required, we have to use C as an interface between Java and FORTRAN. In this discussion C is only used as a means of calling FORTRAN, but of course it is applicable if only Java and C are working together. The Java/C++ interface is very similar to the Java/C interface, so is not discussed. As a GUI is outside the scope of this document, a simple example is used to illustrate the principle involved by simulating a GUI. When some Java code starts executing a GUI is opened. The user can interact with the GUI to send data to a C program via the JNI, and from there on to FORTRAN as follows: GUI --> Java Code --> JNI --> C code --> FORTRAN code The FORTRAN code can then do whatever it is asked to do, and on completing its calculations, it can send graphical or text data back to the GUI as follows: FORTRAN code --> C code --> JNI --> Java Code --> GUI The JNI is documented by SUN at http://java.sun.com/docs/books/tutorial/native1.1/index.html. To call FORTRAN from C is quite easy. Functions and subbroutines can be called as C functions, provided that parameters are called by reference, i.e. as pointers, and not by value. Also note that arrays are offset by 1 in FORTRAN but by 0 in C. Depending on the compiler, the function/subroutine name may have to be appended by a "_", which is the case here. To link Java and FORTRAN via C, proceed with the following steps: (1) Write the Java code, see the test example JavaCode.java, where we generate a 10 element array. 1a) The Java code must contain a declaration for the method name of the C code. In the example here it is sumsquaredc(). 1b) The Java code must also contain a static call to the System.loadLibrary() method. This must be the name of compiled and linked C/FORTRAN code in a static library file, other than the prefix "lib" and the appended ".so". In this example the filename is "libmycodeinc.so", so the name used is "mycodeinc" in the Java code. (2) Compile the Java program by typing "javac" followed by the filename of the Java code. In this case it is: javac JavaCode.java which generates the ".class" file. In this case it will be "JavaCode.class". (3) Generate the JNI header file by typing "javah -jni" + Java code filename, in this case it is: javah -jni JavaCode which will create a header file with the name of the Java code file appended by ".h". In this case it is JavaCode.h, and will be used by the C code. Do not edit this file. (4) Write the C code, which will be the interface with FORTRAN, here it is called CCode.c. Conditional compilation is discussed near the end of this document. 4a) Put #include followed by the name of the header file at the top, in this case it is #include "JavaCode.h". See the example in CCode.c. 4b) Take the prototype declaration from the header file and use it to name the function of the C code that Java will call. This will replace main(), if an existing code is being used. 4c) Insert the declaration of special pointers at the beginning of the C code, and release them at the end. (5) Compile the C code, in this case gcc with Linux 8.0 is invoked by typing: gcc -c -D_REENTRANT -fPIC \ -I/home/csharp/java/j2sdk1.4.2_03/include \ -I/home/csharp/java/j2sdk1.4.2_03/include/linux -c CCode.c which will generate the object file CCode.o with the JNI linked in. In this case the Java 2 developer kit version 1.4.2 is in the path given. (6) Take the existing FORTRAN program and turn the main program into a function or subroutine that can be called from C. Any data that are passed between C and FORTRAN are passed through the parameters. See the test example in FortranCode.f. (7) Compile the FORTRAN 77 program, in this case g77 with Linux Red 8.0 is invoked by typing: g77 -c FortranCode.f which generates the object file "FortranCode.o". (8) Now here is the tricky bit, the FORTRAN and C codes have to be linked and a library created. With the filenames used here it is done with: gcc -shared CCode.o FortranCode.o -lg2c -o libmycodeinc.so which will create the library "libmycodeinc.so", called as "mycodeinc" in the Java program. Note also that "-lg2c" is required in this example as the FORTRAN code produces output. It is possible that other libraries may be required in certain situations. (9) With tcsh or csh type in this example: setenv LD_LIBRARY_PATH /home/csharp/java to set the environment variable to point to the path of the working directory. If bash or ksh are used, instead type: LD_LIBRARY_PATH=/home/csharp/java export LD_LIBRARY_PATH In our case the former is used. (10)Run the program by typing "java" followed by the name of the Java code, in this case type: java JavaCode which will produce this output on the console shell running on our computer called "proteus". What happens is as follows: i) The main() method in JavaCode.java is invoked, a JavaCode object is created, then a 10 element array is created and written to in order to simulate a GUI. ii) The sumsquaredc() method of JavaCode is then called, this invokes the C function Java_JavaCode_sumsquaredc in CCode.c, and passes the array to it. Note that the file JavaCode.h must be included. iii) Pointers in the C code are set up and the FORTRAN code in FortranCode.f is called as a C function, with the array passed as a parameter. iv) The FORTRAN program now executes, calculating the squares of the contents of the array elements, as well the sum of all the squares. v) Control is passed back to the C program, which has to clear the JNI pointers before returning control to Java. vi) Control is returned to Java, which prints out the final values then ends execution. When each code is invoked or returns control to the code calling it, output is generated as here to confirm that this has taken place. (11)To help debugging we may want to test the C and FORTRAN codes before writing the interface with Java. In that case the C code is an ordinary program with the main() function and no JNI pointers. Once this has been tested and debugged, the main() function is replaced with the special JNI function with the JNI pointers added. Another way to perform this, which also enables us to switch between the C code operating as an ordinary C program and one called from Java is to set up a conditional compilation version of CCode.c. On setting the flag JAVA to 1, the C code is set up to be called from Java, when the flag is set to 0 the C code can be called directly by the user as an ordinary C program. The example here is heavily commented, and a "j" or "c" is placed in column 1 to indicate which sections of the code are specific to when it is called by Java, or as an ordinary C code respectively. In the functional code the "j" and "c" in column 1 must be removed with all the #define etc. directives starting in column 1. If the C code does some significant processing on its own, with or without calling FORTRAN, rather than acting just as an interface between Java and FORTRAN as here, it should be possible to write the code so that most of the body of the code is common to Java and non-Java modes of operation, with conditional compilation used only at the beginning and the end. ________________________________________________________________________________ To summarize, for the specific example the following is performed: (1) Create "JavaCode.java" containing the class JavaCode (in this case) that calls the C method sumsquaredc(). Declare sumsquaredc() as native and call System.loadLibrary("mycodeinc"). (2) Type "javac JavaCode.java" to compile and create "JavaCode.class". (3) Type "javah -jni JavaCode" to create "JavaCode.h" header file. (4) Create "CCode.c", the C code interface, with the function name Java_JavaCode_sumsquaredc(). (5) Type "gcc -c -D_REENTRANT -fPIC \ -I/home/csharp/java/j2sdk1.4.2_03/include \ -I/home/csharp/java/j2sdk1.4.2_03/include/linux -c CCode.c" to compile CCode and link it with JNI, producing "CCode.o". (6) Create or modify FortranCode.f. (7) Type "g77 -c FortranCode.f" to generate "FortranCode.o". (8) Type "gcc -shared CCode.o FortranCode.o -lg2c -o libmycodeinc.so" to link the object files and create "libmycodeinc.so". (9) Type "setenv LD_LIBRARY_PATH /home/csharp/java" to set LD_LIBRARY_PATH to point to the working library. (10)Type "java JavaCode" to run all the codes. (11)Conditional compilation for "CCode.c" can be implemented for testing and debugging the C and FORTRAN codes before linking to Java. ________________________________________________________________________________
Return to the Coding & Scripting main page.