Lost in translation

Recently I found myself unexpectedly lost in translation.  As a “native speaker” of several languages of the C family I was facing the problem of calling various functions of a FORTRAN library from my C/C++ program. Although, I had done such kind of mixed-language programming in the past this time something went seriously wrong.  To call a FORTRAN function from C/C++ one has to provide a C/C++ function prototype for each FORTRAN function.  Thus the one-million dollar question was how does, for example, the FORTRAN function declaration

*     .. Scalar Arguments ..
*     .. Array Arguments ..

translate to a C/C++ function prototype?  (This BLAS function calculates the scalar product of two complex vectors.)

Though there is no standard that defines inter-language operability between C/C++ and FORTRAN the answer is: It depends.  It depends on the employed FORTRAN and C/C++ compilers and possibly on the operating system.

First, one has to figure out which C/C++ data types are binary compatible to the various FORTRAN data types.  As mentioned before this is a compiler-dependent issue.  Typically the following mapping applies

  • INTEGER -> signed int
  • REAL -> float
  • DOUBLE PRECISION -> double
  • COMPLEX -> struct { float z[2]; } or float complex (with C99 and header file complex.h) or std::complex<float> (in C++ with header file complex)
  • DOUBLE COMPLEX -> struct { double z[2]; } or double complex (with C99 and header file complex.h) or std::complex<double> (in C++ with header file complex)
  • LOGICAL -> int (Only specific values may be valid representations of  true and false.)

Secondly, the FORTRAN function’s name in C/C++ representation has to be determined.  Again there is no general rule.  Most compilers, however, down-case the FORTRAN function name and add one or two underscores. Furthermore, FORTRAN function arguments are usually passed by pointers. Thus, the C++ function prototype for the ZDOTC FORTRAN function shown above may be

extern "C" {
  std::complex<double> zdotc_(const int *n,
                              const std::complex<double> *zx, const int *incx,
                              const std::complex<double> *zy, const int *incy);

The ZDOTC function does not modify all its input parameters (so-called in-parameters).  Therefore, the pointers in the C++ prototype have been declared as const.  For parameters that may be modified by a FORTRAN function (so-called in-out-parameters) the corresponding C/C++ pointers have to be declared as non-const.

With the explanations given, the translation form the FORTRAN function declaration to the C++ function prototype of ZDOTC looks straight forward.  Some FORTRAN compilers, however, do something quite unexpected, which finally let me feel a little bit lost in translation after getting strange run-time errors when calling ZDOTC from C++ as illustrated above.

Some FORTRAN compilers, in particular g77 and ifort, generate code that requires special treatment for complex return values.  These are returned via an extra argument in the calling sequence that points to where to store the return value.  Thus, the C++ function becomes

extern "C" {
  void zdotc_(std::complex<double> *res, const int *n,
              const std::complex<double> *zx, const int *incx,
              const std::complex<double> *zy, const int *incy);

with possibly a second underscore in the function name.

All these different calling conventions make it hard to write portable C/C++ programs that interact with FORTRAN libraries.  Some FORTRAN compilers even allow to specify on compile-time how to deal with complex return values or to change the byte-width of INTEGER and REAL variables.  This has to be taken into account when specifying the C/C++ function prototypes of FORTRAN functions.

Further reading:

2 thoughts on “Lost in translation

  1. Hello,

    My name is Chris, a long time user of TRNG, which is awesome by the way. I was curious reading this blog post why you didn’t mention the Fortran 2003/2008 standard intrinsic module ISO_C_BINDING.

    It is currently fully implemented by essentially all the major Fortran compilers, and resolves (almost) all of the issues you brought up. For example, it defines standard type-conversions between Fortran and C/C++ types (including complexes), it handles passing parameters by both value and reference, supports transparent passage of object and function pointers between the two, and even (!!!) allows the defintion of C structs matching Fortran derived types (with some natural restrictions).

    The type conversion even takes into account machine architecture, so data-type size is not an issue; this is especially useful because C numeric types, unlike those in Fortran, do not have specified sizes, but only minimums.

    Furthermore, you just write the corresponding C-header naturally, that is, using the exact same function names as in Fortran, specifying parameters as though you were calling C, etc.

    The only real constraint that remains is that you must use matching C and Fortran compilers (i.e. icc and ifort, gcc and gfortran). This is the result of an interesting distinction between how C/C++ and Fortran define their standards: C and C++ don’t just define the required language interface, but also set requirements for implementation of the compiler and runtime (C++ being much stricter in that regard than C). Fortran, on the other hand, for historical reasons, is specified solely in terms of the language API (that is, the interface the programmer sees), and specifies essentially nothing about the actual compiler/runtime implementation. This is the basis for the confusion as to zero, one, or two underscores appended to symbol names, for instance. The caveat of matching compilers is not such a great restriction though, as conformant code will compile just fine using any matching pair, and the resulting binaries are portable.

    All in all, ISO_C_BINDING accomplishes everything you need for C/C++ to Fortran interlanguage communication.

    As I said, all the major Fortran compilers implement the module as an intrinsic module, though the quality of their documentation varies. I would recommend looking at both the gfortran documentation on mixed-language programming, as well as ifort’s (really super-useful) full Fortran language documentation (i.e. it doesn’t just document the ifort compiler, but the full Fortran language standard).

    I hope this was a helpful, if not too long, comment. Have a good Saturday,

    — Chris

  2. Thanks for your reply. The intrinsic module ISO_C_BINDING solves a lot of mixed language programming indeed. It makes it easy to call C functions from Fortran and to write Fortran functions that may be called from C by declaring Fortran variables/arguments as real(c_double), lets say. I am not sure, however, if it is also useful for calling standard Fortran functions with real arguments. In particular, I am not sure if it is save to convert real variables to real(c_double) and vice versa.

Leave a Reply

Your email address will not be published. Required fields are marked *