Fortran_and_cplusplus

2018/07/16

Categories: Programming Tags: C++ FORTRAN

Introduction

Anybody working in the area of numerical programming and engineering software (as opposed to software engineering!) will more than likely have to work with Fortran code. A common procedure now is to integrate such legacy Fortran code into a more modern framework. In the course of my work, I came into contact with quite a large body of Fortran and C++ code. To better understand the interface between the two, I studied in some detail a basic sample application that shows the main ideas related to the construction of such code. I also came into contact with a subtle bug in the course of this process, highlighting some of the problems that can arise when mixing languages.

Description of the Code

Basic Structure and Makefile

The structure adopted here is probably reasonably typical. A block of Fortran code containing data-structures and functions is to be interfaced to a parent program written in C++. Perhaps the easiest part of the project to present is the Makefile

FortCTester: fortran_routines.o cppMain.cpp fortran_routines.h cpp_variables.h
    g++ -oFortCTester -g -ggdb -lm -lg2c fortran_routines.o cppMain.cpp

fortran_routines.o: fortran_routines.f fortran_variables.h
    g77  -g -ggdb  -c fortran_routines.f

clean:
    rm FortCTester
    rm *.o

Here, the final executable is called FortCTester. We compile the Fortran code in fortran_routines.f only to object code (by default in fortran_routines.o) using the GNU g77 compiler. We have debugging turned on so that we can have a poke around in the running program later.

The executable is compiled using the GNU g compiler. Because we're linking Fortran code to C code, we include the name of the fortran object file in this command. Less obviously, we have to link in an external libg2c library using the -lg2c option.

Based on this Makefile, we can compile our project to a running executable. Of course, a Makefile is only a small part of the process of producing a running program, so we turn our attention next to the Fortran and C++ source code.

C++ Headers

The entire source for the project can be downloaded in the form of a gzipped tarball: FortCTester.tar.gz. The most important parts of the C++ code will be excerpted here.

For this example, it makes most sense to begin by discussing the C++ code since it is the main code of the program. In an actual application, often you will begin by reading the Fortran code since the Fortran code will probably have been fully written some time ago, and now you have to repackage it in some way.

As we have seen already, the C++ code is contained in the file cppMain.cpp The first bit of code to give us access to a Fortran function is contained in the included header file fortran_routines.h.

extern "C"
{
    extern void fortran_routine__();
}

Here we are saying that there is an function called fortran_routine__ defined in some other file and we want to be able to access it. You should note here the use of underscores at the end of the function name. In fact (as we shall see) the function is defined in the Fortran code as fortran_function. However, when a variable/function name appears in the C/C namespace it does so with an underscore appended. (http://www.math.utah.edu/software/c-with-fortran.html#routine-naming[This doesn't apply to all Fortran/C compilers, but it's a common convention going back to the work of Stu Feldman]). And if the variable/function name already contains an underscore (like our function does), then it http://www.chiralcomp.com/support/mixing_f77_c_cpp/[gets 2 underscores appended]. If nothing else, this makes it easy to see which entities in our namespace are coming from Fortran-land, and which are native C.

Getting access to Fortran functions is useful, but it is also necessary to get access to data (especially since we are somewhat limited in our ability to access data through return values of functions). The included header file cpp_variables.h contains the declarations necessary to gain access to the structures in the Fortran code. To do this, we declare a struct that has the same layout as the Fortran common block we want access to (we’ll see this later).

struct variables
{
    double     value_double;
    float      value_float;
    int        value_int;
    float      value_matrix[4][2];
    int        value_vector[5];
    bool       value_bool;
};

Although this looks like any other struct you might define, it is crucial that its layout is exactly the same as that of the Fortran common block we will be accessing with it.

To tell our C++ code that we’re going to be accessing something foreign using this structure, we use the declaration:

extern "C" {
    extern struct     variables    vari_;
    };

Again, the trailing underscore is because vari is coming from Fortran namespace. We are telling the C++ code to read data from this foreign entity using the pattern laid down in variables as defined above.

Fortran Headers/Code

As we can see from the Makefile, all the Fortran code is contained in the file fortran_routine.f and in the header file included into this subroutine include fortran_routine.h (Note that the include process used is a nonstandard, but generally acceptable practice).

The most important parts of the Fortran code are contained in include fortran_routine.h. First we must declare all the variables to be used

      real*8        value_double
      real          value_float
      integer       value_int
      real          value_matrix(2,4)
      integer       value_vector(5)
      logical       value_bool

Then we have to parcel these up into a common block as shown:

      common /vari/
     +  value_double,
     +  value_float,
     +  value_int, 
     +  value_matrix,
     +  value_vector,
     +  value_bool
      save /vari/

Notice that we have listed the variables in the same sequence as in the C `struct` definition. This is essential, otherwise you won't get what you want when you read/write to this data structure. You should also notice, as a point of good Fortran programming practice (more of which anon, though I'm no authority) that the types are listed in decreasing order of size (i.e. doubles first!). This is useful in http://www.ibiblio.org/pub/languages/fortran/ch5-3.html[preserving alignment of your data]. Finally, you should be aware that matrices are indexed differently in C/C and in Fortran. The definitions above illustrate the difference.

The only other point to emphasise is that the names fortran_routine and vari must tally with the names we have used to address these entities in the C++ code (trailing underscores notwithstanding).

Making it Work

At this point, we have set up all the infrastructure needed to get shared access to a data structure between C and Fortran, and to call a Fortran function from C. Calling the Fortran function from C++ is as simple as:

fortran_routine__();

Working with the shared variables from Fortran is no big deal, and they are treated like any other variables (that’s all they are to Fortran).

      value_bool=.TRUE.
      value_int=12345
      value_float=1.23
      value_double=5.67
      print*, ' boolean value', value_bool
      print*, ' int value', value_int
      print*, ' real value', value_float
      print*, ' real*8 value', value_double

From C++, we treat our structure like pretty much any other:

vari_.value_bool = true;
vari_.value_int = 12345;
vari_.value_float = 1.23;
vari_.value_double = 5.67;

Mini-Conclusion

And that, as they say, is that. We have successfully attached Fortran code to a C program, and got the two of them talking to one another. The code in the tarfile essentially goes through the motions of setting up the arrangement described above and writing to data-structures from C. It then calls a Fortran routine that prints out the values (to make sure they are recorded correctly) and rewriting the values. When the Fortran portion finishes, C++ then prints out the Fortran-written variables, closing the circle and showing that we have good communication between these two languages.

However, that is not quite the end of the story, and there is still the potential for some unpleasant hiccups. More of which in Part II.