Up | Previous | Next | Title Page | Contents

2.6 Mixing FORTRAN and C

This section describes how to mix code written in both FORTRAN and C. It covers both internal code (used only within one program), and SUBLIB subroutines that must be called from either language.
A subroutine must have a separate interface for each language. A FORTRAN program can only call the FORTRAN interface, while a C program can only call the C interface, because the necessary calling conventions are so different. If you are rewriting a subroutine which is used only internally to your program, you may need an interface for only one language. Some SUBLIB routines are useful only for one language, but most subroutines will have two interfaces.

2.6.1 Bridge Routines

The calling interface for the language of subroutine are straightforward. Follow the rules for that language. The interface for the other language will be implemented via a “bridge” routine, which must be written in C.
The bridge routine accepts arguments in the format of the opposite language, converts them to the form of the subroutine’s language and calls the main subroutine. For example, a subroutine written in C would have a bridge also written in C that accepts arguments in the FORTRAN style, reformat them, and call the main C subroutine. A subroutine written in FORTRAN would have a C bridge that accepts C-style arguments, and call the FORTRAN subroutine in the proper manner.
If you are using ANSI C, do not use function prototypes on FORTRAN bridges (routines written in C that are intended to be called from FORTRAN).

2.6.2 Naming Subroutines

The subroutine names for the FORTRAN and C interfaces must be different. It is not sufficient to add an underscore to the end of a name; the letters themselves must be different. The name difference is forced by the fact that some FORTRAN compilers put a trailing underscore after the name, and others do not.
In order for a C routine to be called from FORTRAN, it must be named in a manner the FORTRAN compiler will recognize. This is handled via the FTN_NAME macro. This macro is used any where the subroutine name would be, including in the declaration of the subroutine. The subroutine name is the argument for the macro; “ftnbridge.h” must be included:
#include "xvmaininc.h"
#include "ftnbridge.h"

void FTN_NAME(mysub)(param1, param2)
int *param1, *param2;                       /* inputs */
{
   zmysub(*param1, *param2);
}
would be called by FORTRAN:
      integer a, b 
      call mysub(a, b)
A FORTRAN routine that wishes to be called by C must have a bridge written in C. Otherwise, the C caller would have to use the FTN_NAME macro to call the routine, which would cause problems if the routine were ever converted to C. The bridge routine takes care of this:
subroutine fsub(param1, param2)
      integer param1, param2                   ! inputs
C     do something
      return

The C bridge looks like the following. Note the use of FTN_NAME when calling the FORTRAN program:

#include "xvmaininc.h"
#include "ftnbridge.h"

void csub(param1, param2)
int param1, param2;                            /* inputs */
{
   FTN_NAME(fsub)(&param1, &param2);
}

It would be called from a C program:

int a, b; 
csub(a, b); 

2.6.3 Passing Numeric Arguments

Passing numeric arguments and arrays between FORTRAN and C is straightforward. The data type equivalencies are listed. See Table 8: FORTRAN declarations for Run-Time Library arguments. A routine that receives data of one type must be passed the equivalent type of data if called from the other language. The bridge may change the datatype between the caller and the routine if desired, but the subroutine interface that actually spans the language change (caller-bridge for FORTRAN to C, bridge-routine for C to FORTRAN) must follow this table.
For numeric arguments FORTRAN passes arguments by reference, while C normally passes input arguments by value. Such arguments must be converted in the bridge routine. A FORTRAN to C bridge declares all arguments as pointers, then puts asterisks (*) in front of the appropriate arguments to dereference them when calling the main routine. A C to FORTRAN bridge declares the appropriate arguments as values (not pointers), and uses an ampersand (&) in the call to the FORTRAN routine to convert them to pointers. There are examples in 2.6.2 Naming Subroutines.
Not all C arguments are passed by value. If a pointer comes in, such as for an output variable or an array, then the pointer can normally be passed unchanged to the subroutine.
FORTRAN Data Type
C Data Type
BYTE
unsigned char
INTEGER*2
short int
INTEGER*4
int
REAL*4
float
REAL*8
double
COMPLEX*8
struct complex {float r, i; }
INTEGER
int
CHARACTER*n
char x[n] *
INTEGER x(m)
int x[m]
REAL x(m)
float x[m]
CHARACTER*n x(m)
char x[m][n] *
* Characters require special handling, see 2.6.4 Passing Strings

Table 9: FORTRAN and C Argument Data Type Equivalence

Two-dimensional (or higher) arrays are handled differently in each language. FORTRAN accesses them in column-major order, while C uses row-major order. The order of the subscripts is reversed. One can either copy the array and reshuffling it in the bridge (which can be inefficient), or simply document the need to reverse the subscripts in the other language when calling the routine.

2.6.4 Passing Strings

Passing FORTRAN strings is not standardized among compilers. The RTL currently supports six different methods of passing FORTRAN strings in arguments. The details of the six methods are hidden in the RTL FORTRAN String Conversion Routines, so they all look the same to the application programmer.
Code that passes a string to or from FORTRAN uses the RTL string conversion routines ( sfor2c and sc2for family). Code that calls the conversion routines must set the FTN_STRING flag in the imakefile.

2.6.4.1 Accepting FORTRAN Strings in C

Writing a C routine that accepts FORTRAN strings as arguments is easy. Use sfor2c for input from a FORTRAN program and sc2for for output to a FORTRAN program.
The sfor2c and sc2for family of routines are described, with examples, in
Typically, a FORTRAN to C bridge routine will first call some of the sfor2c routines to convert all the input strings to C format, then it will call the main C subroutine, and finally it will convert any output strings back to FORTRAN format via the sc2for family of routines.
There is no way currently implemented to return CHARACTER*n variables as the function return of a C subroutine. Any output strings should be included as arguments instead.

2.6.4.2 Accepting C Strings in FORTRAN

The easiest way to accept C strings in a FORTRAN subroutine is to write the subroutine in C instead. It is possible to accept C strings by doing a two-stage bridge routine. The first stage, written in C, handles the FTN_NAME part of the transfer, and passes the string and its length as arguments to the second bridge.
The second bridge, written in FORTRAN, accepts the strings as BYTE arrays and uses the SUBLIB routine mvlc and the passed-in length to convert them to FORTRAN strings, which may then be passed to the FORTRAN main subroutine. To get strings out, the reverse procedure is used. The FORTRAN bridge uses mvcl to write a BYTE array, then passes that and the length back to the C bridge, which puts a null terminator on the string and returns it to the C caller.
Examples of this type of bridge are the SPICE bridge routines, in the file SPBRI.COM. One of the bridges, for bodvar, is presented below. The C-callable first bridge follows:
void zbodvar(body, item, dim, values)

int body;
char *item;
int *dim;
double *values;
{
   int i;
   i=strlen(item);

   FTN_NAME(xbodvar) (&body, item, &i, dim, values);
}
It takes the length string parameter and passes it with the string pointer to the second-stage bridge. It converts scalar and array arguments, and uses the FTN_NAME macro. The second bridge called by code above follows:
subroutine xbodvar(body, item, i, dim, values)

      integer body
      byte item(1)
      integer i
      integer dim
      double precision values(*)
      character*80 text

      text='
      if (i.gt.80) call xvmessage('xbodvar, string too long',' ')

C     Transformation to Fortran-string
      call mvlc(item, text, i)

      call bodvar(body, text, dim, values)

      return
It converts the C string (passed as a BYTE array) and length to a FORTRAN CHARACTER*n variable, which is then passed into the bodvar routine, the FORTRAN -callable version.

2.6.4.3 Machine Dependencies

Machine dependencies can’t always be avoided. Code may be dependent on the operating system (VMS or UNIX), the computer hardware or variety of UNIX. There are significant differences among UNIX implementations.
Machine dependencies may be isolated into separate source files, and compiled only on the relevant machine. Or machine-dependent code can be written in-line, and the C preprocessor used to select the appropriate version.
The first solution, separate source files, is appropriate when there are entire routines that are implemented differently under various operating systems. This option is normally used for differences between VMS and UNIX; only rarely will you have separate source files for variants of UNIX. Filenames end in an underscore and the OS name, for example, ”open_input_file_vms.c” and “open_input_file_unix.c”.
Once you have separate working source files, get the appropriate one to compile during a build by modifying the imakefile.
The MODULE_LIST (or other appropriate macro) is defined based on the machine type. Since vimake uses the C preprocessor, the rules listed below for machine-dependent preprocessor symbols should be used. For example, a program named “prog.c” calls a routine that is OS-dependent, named “sub_vms.c” and “sub_unix.c”:
#if VMS_OS 
#define MODULE_LIST prog.c sub_vms.c 
#define CLEAN_OTHER_LIST sub_unix.c 
#else 
#define MODULE_LIST prog.c sub_unix.c 
#define CLEAN_OTHER_LIST sub_vms.c 
#endif
The CLEAN_OTHER_LIST macro deletes the source code for the module not compiled during a clean-source operation.
The second solution to the machine dependency problem uses the C preprocessor to conditionally compile parts of the code. This method is appropriate for small differences and handling differences between various flavors of UNIX. The necessary preprocessor symbols are defined in xvmaininc.h.
Use the symbols “VMS_OS” and “UNIX_OS” to select VMS or UNIX. Use “#if “ (not “#ifdef”). “#else” may be used to select the other operating system, but the #else clause must apply to UNIX. Put “VMS_OS” in the #if statement.
To open a standard text file with the fopen() routine, VMS requires that you give RMS file type arguments to fopen(). Otherwise, EDT will not be able to access the file properly. For example:
/* Open an output text file */ 
#if VMS_OS 
file = fopen(filename, “w”,”rat=cr”, “rfm=var”); 
#else 
file = fopen(filename, “w”); 
#endif
The file xvmaininc.h defines symbols for all the types of machines VICAR runs on, such as: “VAX_ARCH”, “SUN4_ARCH”, “MAC_AUX_ARCH”, etc., but these are never used in program code. For example, the fstat() system routine returns the optimal blocksize on some machines, but not on others. The Sun 4 and DECstation have it, but Silicon Graphics and HP do not. The mmap() command, is available on Sun 4 and Silicon Graphics, but not on DECstation or HP; the groupings are different.
Machine dependencies based on features, rather than machines is the proper way to solve the problem. To port to a new system, we determine whether or not it has the feature in question, and modify the include files. For example, the symbol “FSTAT_BLKSIZE_OS” defines whether or not fstat() returns the optimal blocksize. “MMAP_AVAIL_OS” defines whether or not the mmap() routine is present. These and other feature defines , which all end in “_OS” by convention, handle machine dependencies. All such definitions end in “_OS”, so searches through the code to find the machine dependencies are easy.
The rule is: conditional compilation statements to handle machine dependencies use names of specific features that are required in the code. These names are defined in standard include files based on the machine type.

Examine xvmaininc.h for current machine dependencies, and check again; it will change. There are machine dependencies that aren't listed in xvmaininc.h. Contact the VICAR system programmer to add the dependency to xvmaininc.h. This is the only include file an imakefile can use.

Since xvmaininc.h is under the control of the VICAR system programmer, it is not always possible for an application programmer to have it modified on short notice. “vmachdep.h” is provided in the portable includes (p2$inc on VMS and $P2INC on UNIX). It contains dependencies that are needed only by application programs. If your definition is in vmachdep.h, it must be included in your source.

Use the style of xvmaininc.h when adding new dependencies to “vmachdep.h”, and make sure you determine the correct setting for every architecture VICAR supports. If you can't find out, contact the system programmer, and if you still can't, make note of the unknowns in the include file. Reserve “vmachdep.h”, add your definition, and redeliver it, even if your application isn't ready for delivery. Then other programmers can modify the file as well. Don’t sit on “vmachdep.h”! Following are examples:

#if FTRUNCATE_AVAIL_OS
   if (ftruncate(devstate->dev.disk.channel, j) != 0)
      status = errno;
   else
#endif
      status = SUCCESS;

...

#if OPEN_PROTECT_OS
   file = open(filename, O_RDWR|O_CREAT, 0666);
#else
   file = open(filename, O_RDWR|O_CREAT);
#endif

...

/* This example shows finding the EOF with and without fstat */

#if FSTAT_AVAIL_OS
#if VMS_OS
#include <stat.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#endif
#else
#include seek_include
#endif
...
#if FSTAT_AVAIL_OS
   struct stat statbuf;
#endif
...
#if FSTAT_AVAIL_OS
   status = fstat(file, &statbuf);
   if (status != 0) return errno;
   eof = statbuf.st_size;
#else                                      /* fstat not available */
   status = lseek(file, 0, SEEK_END);
   if (status == -1) return errno;
   eof = status;
#endif

Up | Previous | Next | Title Page | Contents