AFF --- A container for numbers (array) by Friederich and Forbriger.
Interfacing Fortran 77

Contents of this page:

After switching to C++ many numerical routines coded in Fortran 77 will stay in use. Recoding them in C++ should not generally be considered. It involves a lot of extra work and potentially introduces new bugs and errors to code that was well tested before. Thus some of our C++ functions have to call underlying Fortran 77 code and must exchange data with this code, in particular. Exchanging data also involves passing arrays to and from Fortran.

The example tests/f77test.cc and its associates demonstrates how AFF data may be passed to Fortran and how Fortran arrays may be integrated into AFF.

See also
namespace f77interface
tests/f77interface.cc
tests/f77proto.h
tests/f77procs.f
tests/f77common.inc
tests/Makefile

Some general considerations

Compiler
There are two common Fortran 77 compilers available on Linux platforms: The GNU Fortran 77 (g77) from the GNU compiler collection ("http://www.gnu.org/software/gcc/gcc.html") and the Fortran to C compiler (f2c) published through netlib ("http://netlib.bell-labs.com/netlib/f2c/index.html").
When compiling the C++ part with the g++ from the GNU compiler collection it may seem natural to use g77 for the Fortran part. However, when interfacing the Fortran components, we have to know about the internals of the compiled Fortran code so that the linker will find the components, that we wish to access. g77 does not expose these internals, in particular it may operate in different modes (f2c compatible or not) and is subject to future changes. f2c on the other hand exposes the intermediate C code, which is then compiled by gcc from the GNU compiler collection and may easily be linked to C++ code.
Decision: We use f2c and gcc in our examples of interoparting Fortran code and AFF.
Prototypes
Source code is usually separated into different compilation units (source files). These code fragmemts must be kept synchronous, i.e. all calls to a function must use the same (and correct) formal parameters, all access to global data (e.g. common blocks) must use the same declaration.
While C offers a control mechanism to ensure snychronous code (through prototypes and header files), Fortran 77 does not. A common technique with Fortran is to read common block definitions from extra files (using the include statement), thus ensuring that every function and subroutine uses the same definition. How can we ensure that later changes applied to the Fortran code will be recognized in the interoperating C++ code?
Decision: We make extensive use of header files and prototypes that can be generated with f2c. This makes interface code more complicated (i.e. splits it into more different compilation units) and introduces more header files (e.g. tests/f77common_com.P and tests/f77procs.P) but introduces more safety too.

Aspects to consider:

  1. Passing pointers of Fortran type to pointer of C++ type (reinterpret_cast)
  2. Creating function call prototypes
  3. factoring out interfacing issues to seperate compilation unit and interface functions
  4. creating function call prototypes with extern "C" linkage
  5. creating common block declarations with extern linkage
  6. passing fixed array dimensions from Fortran to C++
  7. passing variable array dimensions from Fortran common block to C++
  8. how to satisfy linker with a MAIN__ function
  9. compiling f2c output with g++ (using -C++ option)

Passing arrays through function calls

An example for passing AFF data to a Fortran function is given in f77interface::fill. The interface function is defined in tests/f77interface.cc and presented in tests/f77proto.h. It is declared

The Array is converted into an aff::FortranArray by

within the function. From this the function retrieves the appropriate Fortran layout of the array.

In case the Fortran code is modfified we only have to keep track within the interface module (i.e. tests/f77interface.cc and tests/f77proto.h).

Aspects that had to be considered:

  • The corresponding Fortran subroutine in tests/f77procs.f is compiled by f2c and g++. Hence we pass the -C++ option to f2c.
  • We have to link against libf2c.a and libm.a or their dynamic companions.
  • We have to provide a MAIN__() function in our code to satisfy the linker (discovered by wolle). This function, however is never called. It must be declared with extern "C" linkage.
  • For calling the Fortran functionality we provide an extra C++ interface. This interface is presented in tests/f77proto.h and namespace f77interface. The definitions are coded in tests/f77interface.cc.
  • To ensure synchronous code between Fortran and the C++ interface, we use prototypes written by f2c. They are generated by
    f2c -C++ -P -\!c tests/f77procs.f 
    to file tests/f77procs.P
  • The prototypes are not declared with external C linkage. Therefore the correspnding include section in tests/f77interface.cc reads like
    extern "C" { #include "f77procs.P" }
  • Additionally we must include f2c.h in tests/f77interface.cc.
  • Within f77interface::fill type conversion must take place. The array shape is derived as C++ int values. They are converted to values of type integer (defined in f2c.h) with statements like
    integer n1=fa.last(0);
    and passed to the Fortran function as pointers of type integer* by
    fill_(pa, &l1, &n1, &l2, &n2, &l3, &n3)
  • The array values are access within the Fortran subroutine through a pointer to the first element. This pointer is of type integer*, which is defined as long int* in f2c.h. From the FortranArray object we receive a pointer of type int*. The only way I see to convert it is via
    integer* pa=reinterpret_cast<integer *>(fa.pointer());
    which is a totally unchecked conversion. Since the type size is known at compile-time, there should be a more elegant way. And there is: Have a look at aff::util::SizeCheckedCast. The more recent version of tests/f77test.cc uses the aff::FortranArray::castedpointer member template function, which again calls a compile-time size-check.

Accessing arrays in common blocks

In many Fortran code modules essential array data is passed through global common blocks between subroutines. It is possible to make this data visible in form of an aff::Array object. However, this is slightly more complicated than passing a C++ array to an underlying Fortran subroutine.

An example for this technique is given by the following functions:

  1. f77interface::fillarray takes two 1D aff::Array<float> arguments and passes them to the Fotran subroutine fillarray which calculates the contents of a complex common block array from them.
  2. f77interface::sums takes the result of the Fortran subroutine sums, which calculates column-sums of the common block array, and returns them as an aff::Array.
  3. f77interface::viewcommon returns an aff::Array that offers direct read/write access to the Fortran common block.

The interface functions are presented in tests/f77proto.h, are defined in tests/f77interface.cc and the underlying Fortran subroutines are defined in tests/f77procs.f. The common-block itself is defined in tests/f77common.inc. f77interface::viewcommon is the module that actually allows direct read/write acces to the common block. The other two provide means to access the same common block from tests/f77test.cc through calls to Fortran subroutines.

Addtionally to the considerations for Passing arrays through function calls we have to discuss the following:

  • f77interface::fillarray passes AFF arrays to Fortran code just in the way f77interface::fill discussed above. Since Fortran doesn't know of const-correctness it takes arguments of type rather than (see also Notes on the const-correctness of arrays).
  • f77interface::sums has to create an AFF array of appropriate size before passing a pointer to the underlying Fortran subroutine. Since the dimensions of fixed sized Fortran arrays are usually given by parameter constants that are defined in include files, these values are not accessible from the C++ code. We therefore introduced a Fortran subroutine comdim, which can be used to read the dimensions of the common-block array.
  • f77interface::viewcommon creates an AFF array, which is simply a reference to the common-block array and has appropriate shape. Shape and representation (i.e. SharedHeap) are craeted separately. The SharedHeap is intialized by
    aff::SharedHeap<Tzvalue> repr(p, shape.memory_size());
    from a pointer p and a size in memory. Using this constructor ensures, that SharedHeap will never try to deallocate the memory block.
  • The common block definition is not visible from C++. To ensure synchronous code after changes being applied to the Fortran code, we must make use of automatic code generation by f2c. We use
    f2c -C++ -f -u -ec tests/f77procs.f
     sed -e 's/^struct/extern struct/' tests/f77common_com.c > tests/f77common_com.P 
    to create a common block definition in tests/f77common_com.P. This definition is read via
    #include"f77common_com.P"
    in tests/f77interface.cc. The common block is then available as
    extern struct { ... } f77common_
    with extern "C" linkage in the C++ code.
  • Notice: The access shape of the AFF array returned by f77interface::viewcommon is defined upon creation of the array. Changing the actual access shape in the common block later (i.e. the common block members na and nb), e.g. by calling subroutine fillarray, is not reflected by the AFF array. This will first be noticed by the next call to f77interface::viewcommon. It is the responsibility of the programmer to avoid inconsistencies due to different access shapes in Fortran and C++.

Links to other helpful information

Fortran 77 Source code

tests/f77common.inc

c this is <f77common.inc>
c ----------------------------------------------------------------------------
c
c Copyright (c) 2002 by Thomas Forbriger (IMG Frankfurt) 
c
c common block for Fortran test
c
c ----
c This program is free software; you can redistribute it and/or modify
c it under the terms of the GNU General Public License as published by
c the Free Software Foundation; either version 2 of the License, or
c (at your option) any later version. 
c 
c This program is distributed in the hope that it will be useful,
c but WITHOUT ANY WARRANTY; without even the implied warranty of
c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
c GNU General Public License for more details.
c 
c You should have received a copy of the GNU General Public License
c along with this program; if not, write to the Free Software
c Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
c ----
c
c REVISIONS and CHANGES
c    22/12/2002   V1.0   Thomas Forbriger
c
c ============================================================================
c
c dimensions for arrays
      integer amax, bmax
      parameter(amax=10,bmax=20)
c
c used elements in each dimension
      integer na, nb 
c
c complex array
      double complex array(amax,bmax)
c
c common block
      common /f77common/ array,na,nb
c
c ----- END OF f77common.inc ----- 

tests/f77procs.f

c this is <f77procs.f>
c ----------------------------------------------------------------------------
c
c Copyright (c) 2002 by Thomas Forbriger (IMG Frankfurt) 
c
c Fortran 77 subroutines
c
c ----
c This program is free software; you can redistribute it and/or modify
c it under the terms of the GNU General Public License as published by
c the Free Software Foundation; either version 2 of the License, or
c (at your option) any later version. 
c 
c This program is distributed in the hope that it will be useful,
c but WITHOUT ANY WARRANTY; without even the implied warranty of
c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
c GNU General Public License for more details.
c 
c You should have received a copy of the GNU General Public License
c along with this program; if not, write to the Free Software
c Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
c ----
c
c REVISIONS and CHANGES
c    22/12/2002   V1.0   Thomas Forbriger
c
c ============================================================================
c
      subroutine fillarray(vec1, vec2, n1, n2)
c
c writes to common block
c
      include 'f77common.inc'
c
      integer n1,n2
      real vec1(n1),vec2(n2)
c
      double complex ime
      parameter(ime=(0.d0,1.d0))
c
      integer i,j
c
      if (n1.gt.amax) stop 'ERROR: first dimension is too large'
      if (n2.gt.bmax) stop 'ERROR: second dimension is too large'
      na=n1
      nb=n2
      do i=1,n1
        do j=1,n2
          array(i,j)=vec1(i)+ime*vec2(j)
        enddo
      enddo
      return
      end
c
c----------------------------------------------------------------------
c
      subroutine sums(vec,nmax,n)
c
c reads from common block
c
      include 'f77common.inc'
c
      complex vec(nmax)
      integer n,nmax
c
      integer i,j
c
      if (nb.gt.nmax) stop 'ERROR: vector too small'
      n=nb
      do i=1,nb
        vec(i)=(0.,0.)
        do j=1,na
          vec(i)=vec(i)+array(j,i)
        enddo
      enddo
      return
      end
c
c----------------------------------------------------------------------
c
      double complex function total(i)
c
c returns a value derived from common block
c
      include 'f77common.inc'
c
      integer i
c
      double complex result
      integer j
c
      if ((i.lt.1).or.(i.gt.na)) stop 'ERROR: illegal index'
      result=(0.d0,0.d0)
      do j=1,nb
        result=result+array(i,j)
      enddo
      total=result
      return
      end
c
c----------------------------------------------------------------------
c
      subroutine fill(a, ld1, n1, ld2, n2, ld3, n3)
c
c fill a three-domensional array that was passed to the subroutine
c
      integer ld1,n1,ld2,n2,ld3,n3
      integer a(ld1,ld2,ld3)
c
      integer i,j,k
c
      do i=1,n1
        do j=1,n2
          do k=1,n3
            a(i,j,k)=i+10*j+100*k
          enddo
        enddo
      enddo
c
      return
      end
c
c----------------------------------------------------------------------
c
      subroutine comdim(maxa, maxb)
c
c we have no access to the defined dimensions of the common block
c this subroutines passes the values to the rest of the world
c
      integer maxa,maxb
c
      include 'f77common.inc'
c
      maxa=amax
      maxb=bmax
c
      return
      end
c
c ----- END OF f77procs.f ----- 

Code generated by f2c

tests/f77common_com.P

#include "f2c.h"

#ifdef __cplusplus
extern "C" {
#endif

extern struct {
    doublecomplex array[200]	/* was [10][20] */;
    integer na, nb;
} f77common_;

#ifdef __cplusplus
}
#endif

tests/f77procs.P

extern int fillarray_(real *vec1, real *vec2, integer *n1, integer *n2);
extern int sums_(complex *vec, integer *nmax, integer *n);
extern Z_f total_(doublecomplex * ret_val, integer *i__);
extern int fill_(integer *a, integer *ld1, integer *n1, integer *ld2, integer *n2, integer *ld3, integer *n3);
extern int comdim_(integer *maxa, integer *maxb);
/* comlen f77common_ 3208 */