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.