3- Argument Passing in Calling C from Fortran
Passing numeric arguments between Fortran and C is straightforward. One only has to remember that Fortran always uses call-by-reference, i.e. it passes the address of the memory location where the value is stored.
Consequently, the Fortran routine call
CALL SUB(X,2)
corresponds to the C function declaration:
void Sub( REAL* xp, INTEGER* ip )
The value itself is accessed as in
float x = *xp;
To store a value back into the Fortran variable X
one has to write in C
*xp = 3.14;
If X
is an array the third element X(3)
(in Fortran) has
to be addressed as xp[2]
, since in C array indices start at 0.
It is important to note that the pointers received as arguments must not be used as local variable. For example, one might be tempted to write:
void Sub( REAL* xp, INTEGER* ip ) { int i; for( i = *ip; i > 0; i-- ) { float x = *xp++; ... } }
where xp
is incremented to point to the next element, which
is perfectly legal C.
But there is a potential problem if Sub
is called from Fortran.
Some machines (e.g. IBM/370 and Convex) use argument blocks,
i.e. the call passes the address of a memory region containing the
argument addresses.
Since a Fortran routine SUB
would have no way the alter the
content of the argument block,
the Fortran compiler treats it as a compile time constant.
As a result, only the first call to Sub
will access the correct
array elements.
The representation of .TRUE.
varies between platforms.
It seems to be safe to test a LOGICAL
input against 0
but output parameters should use the proper .TRUE.
and
.FALSE.
constants.
Returning the C values ~0
or 0
will work in tests such as
IF(L) ...but may fail for
IF(L .EQ. .TRUE.) ...
C entry points can receive Fortran EXTERNAL
arguments as in
EXTERNAL XT CALL SUB(XT)
Most platforms pass directly the address of the routine
typedef void SUBROUTINE(); void Sub( SUBROUTINE* xt )
but some compilers (AIX/370, IBM/370, Apollo, NeXT) store the routine address in a memory cell whose address the called function receives. If we undo the indirection
void Sub( SUBROUTINE** xtp ) { SUBROUTINE* xt = *xtp; ... }
in the routine header we can use afterwards in both cases the same C statement:
(*xt)(...)
for calling the routine through the function pointer.
Using a typedef
name does more than just improve the readability
of the declaration.
For the IBM C/370 compiler it is essential to add
#pragma linkage(SUBROUTINE,FORTRAN)in order to declare that function pointers of type
SUBROUTINE
have
to use the Fortran convention of argument passing.
This is one of the most tricky and tedious part of Fortran/C intermixing.
A Fortran CHARACTER
variable is characterized by two items of
information---the address and the length---and the mechanism
for passing these two values is compiler dependent.
We can label the different mechanisms according to the number of
arguments received by the called routine.
For a SUBROUTINE
with N
parameters in total of which
k
are of type CHARACTER
the following number of arguments
can be passed:
CHARACTER
argument and for each CHARACTER
argument the length
information is added at the end of the regular argument list.
CALL
argument.
With variations on the type of the length indicator these are the three different schemes I have come across up to now. Of course, there are more possible permutations a new Fortran compiler may choose from.
To give a more complete picture we will take the example of the following basic Fortran routine
SUBROUTINE SUB(X,CH,Y) REAL X,Y CHARACTER*(*) CH
and give the different headers, according to machines, for the
C equivalent function in order
to make it Fortran callable without any problem.
In each case we will end up in the C function
with the fixed variable names
char* ch_ptr
and int ch_len
containing the address and
length of CH
.
VMS
VMS passes the address of a string descriptor containing the address and length information as structure elements:
#include <descrip.h> void Sub( REAL* x, struct dsc$descriptor_s* ch_dsc, REAL* y ) { char* ch_ptr = ch_dsc->dsc$a_pointer; int ch_len = ch_dsc->dsc$w_length; ... }
Cray
Cray packs both items into the 64-bit word received for each argument (and the bit fields used are actually processor dependent):
#include <fortran.h> void Sub( REAL* x, _fcd ch_dsc, REAL* y ) { char* ch_ptr = _fcdtocp(ch_dsc); int ch_len = _fcdlen(ch_dsc); ... }
VS-Fortran
IBM VS-Fortran uses the 2N
scheme with
call-by-reference for the length information:
#pragma linkage(SUB,FORTRAN) void Sub( REAL* x, char* ch_ptr, REAL* y, int* x_size, int* ch_ref, int* y_size ) { int ch_len = *ch_ref; ... }
NeXT
For the Absoft Fortran compiler on NeXT it looks similar except that call-by-value is used:
void Sub( REAL* x, char* ch_ptr, REAL* y, int x_size, int ch_len, int y_size ) { ... }
Others
The bulk of Fortran compilers add the length as int
only for
type CHARACTER
:
void Sub( REAL* x, char* ch_ptr, REAL* y, int ch_len ) { ... }
Apollo
The only remaining exception is the Apollo ftn
compiler which
passes the length in a short*
:
void Sub( REAL* x, char* ch_ptr, REAL* y, short* ch_ref ) { int ch_len = *ch_ref; ... }
KUIP offers a number of convenience functions (fstrdup
,
fstrset
, ...) to convert between fixed length, blank-filled Fortran
CHARACTER
buffers and null-terminated, blank-trimmed C strings.
For details refer to the KUIP manual.