8 More on Calling FORTRAN from C

 8.1 Thread Safety

The operations needed to write a C routine that can call a FORTRAN subroutine or function are fairly similar to those needed when calling C from FORTRAN. Many of the macros that are used are the same, so you should read

Section 5 before reading this.

A typical reason to call FORTRAN from C is to use a pre-existing subroutine library. Here is an example of calling PGPLOT from a C main program.

Example 7 – Passing arguments from C to FORTRAN.
C main program:
  #include "f77.h"
  
  extern F77_SUBROUTINE(pgbegin)
    ( INTEGER(unit), CHARACTER(file), INTEGER(nxsub), INTEGER(nysub)
      TRAIL(file) );
  
  extern F77_SUBROUTINE(pgenv)
    ( REAL(xmin), REAL(xmax), REAL(ymin), REAL(ymax), INTEGER(just),
      INTEGER(axis) );
  
  extern F77_SUBROUTINE(pglabel)
    ( CHARACTER(xlab), CHARACTER(ylab), CHARACTER(toplab)
      TRAIL(xlab) TRAIL(ylab) TRAIL(toplab) );
  
  extern F77_SUBROUTINE(pgpoint)
    ( INTEGER(n), REAL_ARRAY(xs), REAL_ARRAY(ys), INTEGER(symbol) );
  
  extern F77_SUBROUTINE(pgend) ( );
  
  extern F77_SUBROUTINE(pgline)
    ( INTEGER(n), REAL_ARRAY(xpnts), REAL_ARRAY(ypnts) );
  
  main()
  {
    int i;
    float xs[] = {1.,2.,3.,4.,5.};
    float ys[] = {1.,4.,9.,16.,25.};
  
    DECLARE_INTEGER(unit);
    DECLARE_CHARACTER(file,10);
    DECLARE_INTEGER(nxsub);
    DECLARE_INTEGER(nysub);
    DECLARE_REAL(xmin);
    DECLARE_REAL(xmax);
    DECLARE_REAL(ymin);
    DECLARE_REAL(ymax);
    DECLARE_INTEGER(just);
    DECLARE_INTEGER(axis);
    DECLARE_CHARACTER(xlab,50);
    DECLARE_CHARACTER(ylab,50);
    DECLARE_CHARACTER(toplab,50);
    DECLARE_INTEGER(n);
    DECLARE_REAL_ARRAY(xpnts,60);
    DECLARE_REAL_ARRAY(ypnts,60);
    DECLARE_INTEGER(symbol);
  
  
    unit = 0; cnfExprt( "?", file, file_length); nxsub = 1; nysub = 1;
    F77_CALL(pgbegin) ( INTEGER_ARG(&unit), CHARACTER_ARG(file),
                        INTEGER_ARG(&nxsub), INTEGER_ARG(&nysub)
                        TRAIL_ARG(file) );
  
    xmin = 0.0; xmax = 10.0; ymin = 0.0; ymax = 20.0; just = 0; axis = 1;
    F77_CALL(pgenv) ( REAL_ARG(&xmin), REAL_ARG(&xmax), REAL_ARG(&ymin),
                      REAL_ARG(&ymax), INTEGER_ARG(&just), INTEGER_ARG(&axis) );
  
    cnfExprt( "(x)", xlab, xlab_length );
    cnfExprt( "(y)", ylab, ylab_length );
    cnfExprt( "PGPLOT Example 1 - y = x\\u2", toplab, toplab_length );
    F77_CALL(pglabel) ( CHARACTER_ARG(xlab), CHARACTER_ARG(ylab),
                        CHARACTER_ARG(toplab)
                        TRAIL_ARG(xlab) TRAIL_ARG(ylab) TRAIL_ARG(toplab) );
  
    n = 5;
    for( i=0 ; i<n ; i++ )
    {
       xpnts[i] = xs[i];
       ypnts[i] = ys[i];
    }
    symbol = 9;
    F77_CALL(pgpoint) ( INTEGER_ARG(&n), REAL_ARRAY_ARG(xpnts),
                        REAL_ARRAY_ARG(ypnts), INTEGER_ARG(&symbol) );
  
    n = 60;
    for( i=0 ; i<n ; i++ )
    {
       xpnts[i] = 0.1 * i;
       ypnts[i] = xpnts[i]*xpnts[i];
    }
    F77_CALL(pgline) ( INTEGER_ARG(&n), REAL_ARRAY_ARG(xpnts),
                       REAL_ARRAY_ARG(ypnts) );
  
    F77_CALL(pgend)();
  
  }

This is a realistic example of calling PGPLOT routines from C. The module begins with a set of function prototypes for the FORTRAN routines that will be called in the C main program. All variables that need to be passed to FORTRAN subroutines are declared using DECLARE_type macros. These macros ensure that the variables are declared to be of the correct type and storage size expected by the FORTRAN subroutine. There then follow the calls to the subroutines that do the actual plotting. The most notable things about these calls is that the actual arguments are explicitly passed by address. This seems strange to a FORTRAN programmer, but is natural to a C programmer. Arguments that may be modified must always have their addresses passed, not their values. It may be thought that the type_ARG macros should add the & character where it is needed. However, this gives rise to problems when calling FORTRAN from C from FORTRAN, as well as being rather misleading. Note that scalar arguments need the ampersand character adding, whereas array arguments do not. This is exactly what would be typed if the called routine were a C function.

What is clear from this example is that the inability to put arguments that are constant expressions directly in the call to the routine makes the program a lot more verbose than the equivalent FORTRAN program. Unfortunately, the obvious solution of writing an actual argument as something like INTEGER_ARG(&5) does not work as you cannot take the address of a constant. This is not a failing of the F77 macros, but is inherent in the C language. For routines that are called in many places, it will be more convenient to write a wrap-up function in C that is to be called from the C main program and to put all of the F77 macros required into that function. This produces less efficient code, since there is an extra level of subroutine call. However, in many situations, the extra cost will be outweighed by the benefits of more transparent code.

The macro F77_CALL actually expands to the same thing as the macro F77_EXTERNAL_NAME, but is included as it is more descriptive of what is being done when calling a FORTRAN routine from C.

8.1 Thread Safety

Fortran code is not thread-safe, and therefore any C code that calls Fortran code will not be thread-safe unless extra work is done to make it so. The F77_LOCK macro is provided for this purpose. The argument to the macro is a block of code to be run. CNF defines a single global pthread mutex. The F77_LOCK macro firsts locks this mutex, then executes the code specified in its argument, then unlocks the mutex. If another thread already has the mutex locked, then the calling thread will block until the mutex is unlocked.

So any C code that may potentially need to be executed in a threaded context (for instance, C wrappers for Fortran subroutine libraries) should use the F77_LOCK macro to invoke each Fortran call:

  F77_LOCK ( F77_CALL(silly2)( REAL_ARG(&a), REAL_ARG(&b),
                              INTEGER_ARG(&i), INTEGER_ARG(&j),
                              CHARACTER_ARG(fline), INTEGER_ARG(&fline_l),
                              LOGICAL_ARG(&x) TRAIL_ARG(fline) ); )

If this is done consistently, then it ensures that no two threads will attempt to run any Fortran code simultaneously.