|
Mixing FORTRAN and C[1] |
This chapter discusses how a FORTRAN routine can call
code that is written in C
and vice versa.
First, we will discuss how to call C functions from
FORTRAN programs. Essentially, this section discusses how to write a C
procedure so that a FORTRAN pro gram can call it successfully.
By default, the FORTRAN program converts function and subroutine names to lowercase and appends an (underscore). The C compiler never performs any case conversion and does not append an underscore. Therefore, if a FORTRAN program calls a C procedure with the statement:
CALL PROCNAME()
the C procedure must be named procname_. The C procedure may or may not return a value (i.e., its type may be void); any returned value will be ignored. If a FORTRAN program calls a C function with the statement:
X=FNNAME()
then the C procedure must be named fnname_. The C procedure must return a value (i.e., its type must not be void).
Unfortunately, the additional underscore makes it impossible for FORTRAN pro grams to call most UNIX system functions directly. FORTRAN equivalents for the most common system functions are available; they are described in Section 3F of the UNIX Programmer’s Reference Manual. In the more general case, you will have to write your own FORTRAN binding for the system functions that interest you; that is, you will have to write a C routine (with a name ending in an under score) that calls the system function you want. For example, if you want to make the low-level write system call from FORTRAN, you will have to write an intermediate function in C (for example, c_write_) that passes its arguments along to write appropriately.
Remember that names appearing in FORTRAN programs are
converted to lower case by default. If the compiler is invoked with the -U
option, this conversion does not take place. Capitalization of the functions
name in C must match the capitalization used in FORTRAN. Therefore, if the C
function’s name has capital letters, you must compile the FORTRAN program with
the-U option.
FORTRAN routines pass
arguments by reference; they pass a pointer to the actual argument, rather than
the value of the argument themselves. When a FORTRAN program calls a C
function, the C function’s arguments must be declared as pointers to the
appropriate data type. For the FORTRAN scalar data types INTEGER, INTEGER*2,
REAL DOUBLE PRECISION, and LOGICAL, there is a simple correspondence between
the type of the FORTRAN actual argument and the type of the argument in the C
procedure. The following table shows this correspondence
|
FORTRAN Type |
C Type |
|
INTEGER I |
int *i |
|
INTEGER*2 N |
short *n |
|
REAL Z |
float *z |
|
DOUBLE PRECISION D |
double *d |
|
LOGICAL LO |
int *lo |
For example, the FORTRAN statements:
INTEGER I
INTEGER*2 J
REAL X
DOUBLE PRECISION D
LOGICAL L
CALL VEXP( I, J, X, D, L)
can call a C procedure defined as:
void vexp_( i, j, x, d, 1)
int
*i;
short *j;
float *x;
double *d;
int *1;
{
…Program text...
}
The arguments to the C procedure are declared as pointers because FORTRAN programs pass their arguments as pointers (i.e., by reference) by default and C programs pass arguments by value. Therefore, an argument passed to a C procedure from a FORTRAN program must be declared as a pointer explicitly. Note that there is not a simple correspondence between CHARACTER data and any C type; procedure calls that pass CHARACTER data are a special case, which we will explain later.
For the FORTRAN arrays of types INTEGER, INTEGER*2, REAL, DOUBLE PRE
CISION, and LOGICAL, there is a simple correspondence between the type of the
FORTRAN actual argument with which a C procedure is called and the type of the
argument in the C procedure. The following table shows this correspondence:
|
FORTRAN Type |
C Type |
|
INTEGER X( ) |
int x[ ] |
|
INTEGER*2 X( ) |
short x[ ] |
|
REAL X( ) |
float x[ ] |
|
DOUBLE PRECISION X( ) |
double x[ ] |
|
LOGICAL X( ) |
int x[ ] |
|
LOGICAL*2 X( ) |
short x[ ] |
For example, consider the following FORTRAN call:
DIMENSION I(100), X(15O)
CALL ARRAY( I, 100, X, 150)
The C procedure array may be defined as follows:
Array_( i, isize, x, xsize)
int i [ ];
float x[ ];
int *isize, *xsize;
{
…Program text …
}
The C procedure should take these factors into account:
· FORTRAN
and C access arrays differently. FORTRAN organizes arrays in column-major order
(i.e., the first dimension of a multiple-dimensioned array varies the fastest).
C organizes arrays in row-major order (i.e., the last dimension varies the
fastest).
· FORTRAN
array indices start at I, by default; C indices start at 0. Unless you declare
the array otherwise, the FORTRAN element X(1) corresponds to the C element x[0].
· Array arguments to the C procedure do not need to be declared as pointers. Arrays are always passed by reference.
If you pass a CHARACTER argument to a C procedure, the called procedure must be declared with an extra integer argument at the end of its argument list. This argument will be the length of the character variable. An additional integer argument will be added for each CHARACTER argument that is passed to the called procedure. The C type corresponding to CHARACTER is char. For example, consider the following FORTRAN call:
CHARACTER*(*) Cl
CHARACTER*5 C2
REAL X
CALL CHARMAC(Cl, X, C2)
The C procedure corresponding to this call must be declared:
Charmac(c1, x, c2, n1, n2)
integer n1, n2;
char *c1 *c2;
float *x;
{
…Program text...
}
where ni and ii2 are the lengths of the character strings c1 and c2. These additional arguments are passed by value, not by reference.
To pass an argument of type COMPLEX or DOUBLE COMPLEX to a C procedure, the corresponding argument in the C procedure must be declared as follows:
struct { float real, imag; } *complex;
or:
struct { double real, imag; } *dcomplex;
depending on whether the FORTRAN actual argument is COMPLEX or DOUBLE COMPLEX. For example, the FORTRAN statements:
DOUBLE COMPLEX DC
COMPLEX C
CALL COMPL(DC,C)
call a C routine declared as:
compl(
dc, c)
struct
{ double real, imag;} *dc;
struct { float real, imag; } *c;
{
... Program text...
}
FORTRAN does not normally have any structured data types. Therefore, it is (strictly speaking) impossible to pass a structure to a C routine. By default, C passes structures by value, which is also something FORTRAN cannot do. There is, however, a workaround that can handle the most important cases. Relatively few C routines actually pass structures by value; most pass a pointer to a structure. Therefore, you can define a common block that matches the structure you want to pass and then use the first element of the common block (which is the base address of the common block) as an argument. For example, the FORTRAN statements:
COMMON /STRUCP/A, I, F, N
CALL CFUNCT(A)
can be used to call cfunct_, defined as:
void cfunct_(s)
struct
s_ {
float a;
int i;
float f;
int
n;
} *s;
{
/* text of cfunct_ */
}
For all types except CHARACTER, COMPLEX, and DOUBLE COMPLEX, a C procedure called as a function must return a value of the C type corresponding to whatever type the FORTRAN program expects. The following table shows this correspondence:
|
FORTRAN Type |
C Type |
|
INTEGER*2 |
short |
|
INTEGER |
int x |
|
LOGICAL |
int |
|
REAL |
float |
|
DOUBLE PRECISION |
double |
For example, consider these FORTRAN statements:
INTEGER IRET, CFUNCT
IRET CFUNCT()
The C routine cfunct_ should be declared as:
int cfunct_();
If a FORTRAN program expects a function to return data of type CHARACTER, the FORTRAN compiler adds two additional arguments to the beginning of the called procedure’s argument list. The first of these arguments is a pointer to the location in which the called procedure should store the result, The second is the maximum number of characters that should be returned. The called routine should have type void, which means that its returned value is ignored. It is the called procedure’s responsibility to copy its result through the address specified in the first argument.
For example, consider the code:
CHARACTER*10 CHARS, MAKECIIARS
DOUBLE PRECISION X, Y
CHARS=MAKECHARS( X,Y)
TheC routine Makechars_ must be written as follows:
void makechars (result, length, x, y);
char *result;
int
length;
double *x, *y;
{
...Program text, producing returnvalue...
for (I = 0; i<length; i++){
result[i] = returnvalue[i];
}
}
Note that:
• The arguments length and result do not appear in the FORTRAN
statement calling MAICECHARS; they are added by the compiler.
• It is the responsibility of the called routine to copy the result
string into the location specified by result. The called routine must not copy
more than length characters.
• The called procedure has type void.
If a FORTRAN program expects a procedure to return a COMPLEX or DOUBLE COMPLEX value, the FORTRAN compiler adds an additional argument to the beginning of the called procedures argument list. This additional argument is a pointer to a location at which the called procedure must store its result. The called procedure’s returned value will be ignored. For example, consider the code:
COMPLEX BAT, WBAT
REAL X, Y
BAT=WBAT ( X, Y)
The C routine wbat_ must be written as follows:
void wbat_ (location, x, y);
struct (float real, imag; } *location;
float *x, *y;
float realpart;
float imaginarypart;
…Program text, producing realpart
and imaginarypart…
*location.real = realpart;
*location.imag = imaginarypart;
Again, note that the argument location to what_ does not appear in the
FORTRAN call to WBAT; it is added by the compiler. It is the responsibility of
the C subrourtine to copy the result’s real and
imaginary parts correctly into the location specified by the argument location.
If this were a function returning a DOUBLE COMPLEX value, the type float would be replaced by the type double in the definition of wbat_.
When a C routine calls a FORTRAN procedure, it must:
• Convert the procedure’s name to lowercase (unless the FORTRAN code
was compiled with the -U option, in which case the capitalization in C and
FORTRAN must match).
• Append an (underscore) to the FORTRAN procedure’s name.
•Pass all arguments as pointers.
For example, consider the following C code:
main()
{
extern float ftnfn_();
int
in;
float flo;
x = ftnfn_(&in, &flo);
}
This program calls the function FTNFN, which must be defined as:
REAL FUNCTION FTNFN(I,F)
INTEGER I
REAL F
FTNFH = returned-value
END
The following table shows the correspondence between C data types and FORTRAN data types:
|
C Type |
FORTRAN Type |
|
short *x |
INTEGER*2 X |
|
int* x |
INTEGER*4 X |
|
double*x |
DOUBLE PRECISION X |
|
float*x |
REAL |
|
struct (float real,imag;); *x |
COMPLEX*8 X |
|
struct (Dfloat real,imag;); *x |
COMPLEX*16 X |
Arrays in C are always passed by reference. The corresponding dummy
argument in a FORTRAN subprogram should be an array of the corresponding type.
FORTRAN generally does not have data structures, although some manufactures obsessed with VAX/VMS compatibility may have added them. In general, a C routine cannot return a structure to a FORTRAN caller, nor can C call a FORTRAN program with a structure as an argument. If you need to do this and cannot modify the C program, you must write an intermediate C procedure to handle the transition between C and FORTRAN.
The C and FORTRAN compilers can be invoked via either the f77 or the cc command. There is one crucial difference between cc and f77: the two programs mvoke the linker, ld, in different ways. Use cc to invoke the linker if the program’s main, or top-level, routine is written in C; use f77 if the main routine is written in FORTRAN. Using the proper compilation command links the program to the correct initialization and run-time support routines.
For example, the following C program calls the FORTRAN subroutine FORTIO:
main ()
{
extern void fortio_();
extern void f_init(););
f_init();
/*initialize the FORTRAN I/O library*/
fortio(); /*perform FORTRAN I/O*/
The following command compiles the FORTRAN and C source files main.c and fio.f, links them with the proper libraries, and generates an executable (a.out) file:
% cc main.c fio.f –lI77
The program’s main procedure is written in C; therefore, c must be used
to link the object modules and produce the executable file The additional -l option ensures the FORTRAN 1/0 library will be present.
[1] Mike Loukides, UNIX for FORTRAN Programmers, O’ Reilly and Associates, Inc, 103 Morris Street, Sebastopol, CA 95472, (1990, 1991) pp 209-218
I paraphrase a bit, but this is “essentially” from his book.