Fortran 2003 introduced facilities to allow a Fortran program to interact with C in a standardised way.
Fortran has introduced the idea of interoperable entities, which have declarations which have an analogue in C. (Strictly C, not C++. However, as there is a subset of C which is also valid C++, one can also communicate with C++.)
Facilities for ensuring that data objects and procedures are interoperable
are provided by the inrinsic module iso_c_binding
For integer
, real
and complex
types, iso_c_binding
provides names
for constants which are the relevant kind parameters for Fortran. For
example, integer interoperable types include:
! declaration ... interoperable with ...
integer (c_int) :: i_int ! C "int"
integer (c_short) :: i_short_int ! C "short int"
integer (c_long) :: i_long ! C "long int"
integer (c_size_t) :: i_size_t ! C "size_t
The value can be -1 (no interoperable Fortran type) or -2 (no corresponding
C type). Recall size_t
is usually an unsigned type in C. There is still no
direct ananlogue of unsigned types in Fortran, although an interoperable
type is almost certainly available.
For real types, the full list is:
! declaration ... interoperable with ...
real (c_float) :: r_float ! C "float"
real (c_double) :: r_double ! C "double"
real (c_long_double) :: r_long_double ! C "long double"
If the value of the C name is -1 (e.g., c_long_double
) then Fortran does not
provide a precision equal to that of C type (long double
). Other negative
values indicate unavailabillity for different reasons.
For complex types, the full list is:
! declaration ... interoperable with ...
complex (c_float_complex) :: z_float_complex ! C "float _Complex"
complex (c_double_complex) :: z_double_complex ! C "double _Complex"
complex (c_long_double_complex) :: z_ldc ! C "long double _Complex"
The precision for complex numbers relates to the precision for each of the real and imaginary parts.
There is one interoperable logical type:
! declaration
logical (c_bool) :: logical_c_bool ! C "_Bool"
As logical operations in C are often performed using integer types, an integer type may be appropriate in a given context.
A program is provided which prints out a full list of the symbols
from iso_c_binding
illustrated above.
$ ftn print_iso_c_binding.f90
An interoperable character type is expected to be available:
! declaration ... interoperable with ...
character (kind = c_char, len = 1) :: char ! C "char"
To pass strings to C, rather than single characters, it is usually
necessary to think about a string in Fortran as being an assumed
size array of characters of len = 1
.
Named constants for the following C special characters are provided:
c_null_char
(\0
), c_alert
(\a
), c_backspace
(\b
),
c_form_feed
(\f
),c_new_line
(\n
), c_carriage_return
(\r
),
c_horizontal_tab
(\t
), and c_vertical_tab
(\v
).
Fortran code receiving strings from C may wish to discard the c_null_char
at the end of the string.
Suppose we wish to call the standard C library function
int atoi(const char * str);
from Fortran. To do this, we will write a Fortran interface to reflect the need for interoperable arguments. This might be:
interface
function c_atoi(str) bind(c, name = "atoi") result(i)
use, intrinsic :: iso_c_binding, only : c_char, c_int
character (kind = c_char, len = 1), intent(in) :: str(*)
integer (kind = c_int) :: i
end function c_atoi
end interface
The bind(c)
declaration indicates that this interface relates to a C function.
If the name
is present, it can be used to associate the C name with the Fortran
interface declaration. If the name
is not present, the C name will be taken
to be the Fortran name (in lower case). Here,
the function could be called from Fortran via, e.g.,
integer (c_int) :: i
i = c_atoi("-23")
The arguments of the function, and the return type, should all be relevant interoperable types.
A C function with void
return type should have an interface defining a subroutine
rather than a function.
Many C functions have non-pointer dummy arguments, e.g.,
double hypot(double x, double y);
where actual arguments would be passed by value.
An appropriate Fortran interface can provide information on such scalar arguments
via the value
attribute:
interface
function c_hypot(x, y) bind(c, name = "hypot") result(z)
use iso_c_binding, only : c_double
real (c_double), value :: x, y
real (c_double) :: z
end function c_hypot
end interface
The value
attribute takes the place of intent(in)
(they cannot occur
together). If a scalar argument does not have the value
attribute, it
means that C expects a pointer.
The value
attribute can also be used in a normal Fortran
context, where it is a signal to the compiler that the value should be
copied in, whereon it may be changed, but not copied out again.
Suppose we have a C function with prototype
int c_snprintf_float(char * str, size_t nsz, const char * format, float x);
which we wish to call from Fortran. The function uses the standard C
library call snprintf()
to write the value of the float
argument
to a string str
with C format specification format
. The maximum
number of characters to be written is nsz
. The return value is the
number of characters actually written to the string (not including
the \0
terminating character).
The C function is supplied in the current directory; it needs to be compiled (not linked) with the relevant C compiler, e.g.,
$ cc -c c_snprintf.c
which will produce an object c_snprintf.o
.
Write a program which includes an interface which allows the C function
to be called with appropriate arguments. If the program is called
f_snprintf.f90
, we should be able to compile this with the C object
via:
$ ftn c_snprintf.o f_snprintf.f90
Note this is the Fortran compiler.
What can you say about the length of the string returned in Fortran compared with the number of characters written indicated by the return value?
If you were to implement interfaces for both the c_snprintf_float()
and c_snprintf_double()
versions, you might wonder whether you could
overload the specifc names with a generic name.
Fortran arrays of non-zero size are interoperable if the data type is
interoperable, and the array has an explicit shape or an assumed size
(the *
notation).
As an example of an explicit shape consider the C function with prototype
void c_array1(int nlen, double * data);
As usual, one would expect the array to be indexed from zero in C.
An appropriate Fortran interface might be
interface
subroutine c_array1(nlen, data) bind(c)
use iso_c_binding, only : c_int, c_double
integer (c_int), value :: nlen
real (c_double), intent(inout) :: data(nlen)
end subroutine c_array1
end interface
A Fortran allocatable array should be declared as assumed size in the interface. The relevant current size of the allocation will typically need to be passed as well, as above.
For arrays of rank 2, we must remember that the array element order is reversed in C relative to Fortran. That is, the Fortran declaration
integer (c_int) :: array(m, n)
would need to correspond an array array[][m]
or array[n][m]
to be
interoperable. The Fortran interface would specify dimension (m,*)
or dimension(m,n)
, respectively.
The accompanying C file c_array.c
holds a function with prototype
void c_array(int mlen, int nlen, int [][mlen]);
intended to be interoperable with a Fortran array declared (mlen,nlen)
.
The C function simply prints out the values of the elements.
Write a Fortran program that passes a small array of shape (2,3)
to C. Initialise the values consistent with Fortran array element
order (e.g., indicative of increasing address).
Derived types c_ptr
and c_funptr
are provided for interoperability
with C pointer types. A number of functions are provided to manage the
translation of Fortran entities to and from these new types.
-
c_loc(x)
returns thec_ptr
type which C can use as the address of the argument. The argument can be a scalar, an contiguous array of non-zero size (or allocated non-zero size), or an associated pointer. The argument must be of interoperable type. The argument must be a pointer or a data object with target attribute. -
c_funloc(p)
can return the address of an interoperable procedure. -
c_associated(c_ptr1 [, c_ptr2])
if an analogue of theassociated()
intrinsic which returns.true.
if the first argument is notc_null_ptr
. If the second argument is present, the function will return.true.
if both arguments are the same. -
c_f_pointer(c_ptr, fptr [, shape])
provides functionality to tranlate ac_ptr
type into a Fortran pointer. A rank 1 integer array shape is required iffptr
is an array. -
c_f_procpointer(c_funptr, fptr)
provides similar functionality for a procedure pointer.
To be interoperable, a Fortran derived type must map to a plain C struct with interoperable compments. This means the Fortran type must have no type-bound procedures, cannot be extended, cannot have components that have either the allocatable or pointer attributes,
The type should be declared as bind(c), e.g.,
type, bind(c), public :: my_type
integer (c_int) :: icomponent
real (c_float) :: fcomponent
end type my_type
The presence of the bind(c)
means the type cannot be extended.
Let us suppose we have a C program whioch defines a struct
typedef struct array_s {
int nlen;
float * data;
} array_t;
to aggregate the information on an array of float
which is
to be allocated by the program. Further, we wish to call a
subroutine declared in C as
void f_subroutine(const array_t * a);
which we wish to write in Fortran.
A corresponding subroutine in Fortran requires the definition of the interoperable structure, i.e.,
type, bind(c) :: array_t
int (c_int) :: nlen
type (c_ptr) :: data
end type array_t
where we have used a c_ptr
type to represent the C pointer component.
An interoperable subroutine declared bind(c)
might be
subroutine f_subroutine(a) bind(c)
type (array_t), intent(in) :: a
real (c_float), pointer :: data(:)
call c_f_pointer(a%data, data, [ a%nlen ])
! ... perform operations with data(:) ...
end suroutine f_subroutine
The information that the C data is of type float
appears in the declaration
of the Fortran pointer used to access the array. This must be initialised by
a call to c_f_pointer()
before it can be used.
Check you can construct a working example based on the above outline. Initialise some sample values and chcek you can recover the values in the Fortran subroutine.
Try using either the C or the Fortran compiler to perform the link stage.