Fortran 2003: teaching Fortran and C to be friends

Fortran has written a huge amount of code, debugged and working for many years. I am not going to raise the question “which is better - Fortran or C?”. Each language has its own strengths and weaknesses. But, given the wide spread of the C language, cases of “hybrid” applications are becoming more and more popular in certain circles, when part of the code is written (rather, already written) in Fortran and the other in C. But these languages have a certain specificity, about which I partially spoke, and for the application that we wrote to work correctly, you need to consider many nuances. Differences in data types, calling convention, naming convention make the task of creating a mixed language application far from trivial. It’s good that Fortran 2003 introduced a whole set of tools specifically designed to solve the interoperability problem between C and Fortran. By the way, I don’t remember other languages that would standardize this kind of work - another “plus” to Fortran for his outstretched “hand of friendship”.
What is this inter-opera and so on? The term “interoperability” means the possibility of calling function C in Fortran code and vice versa. In addition, you can use global variables, as well as declare local variables, data structures and enumerations that have a correspondence in C. The main idea is that everything should work the same in both C and Fortran. It is worth noting that C means the standard C99 (ISO / IEC 9899: 1999). By the way, Fortran has the right compiler implementation to choose which of the C compilers to be friends with. In the case of Intel Fortan, it is Microsoft Visual C ++ on Windows and gcc on Linux and OS X. What about Intel C ++? Since it is compatible with Visual C ++ and gcc, there are no problems with it (which is to be expected).
Fortran implements support for the same interoperability through the following means:
- Restrictions on data types that may be interoperable
- BIND Construction (C)
- ISO_C_BINDING module
- Attribute VALUE
One of the main difficulties in developing "mixed" applications is that the types in C and Fortran are different: the concepts of pointers, working with strings, with pointers to functions, and so on. And with the basic types is not so simple. Let's say in C we have types from short int to long long int . They may or may not have analogues in Fortran.
To make all this work well, a module was added to Fortran responsible for the “friendship” of these languages - ISO_C_BINDING . What is there? A set of tools that allows you to ensure that the data types in Fortran are “correct” for working with C code.
For example, for the int type, we can use the INTEGER (C_INT) type in Fortran, and C_INTdefined in module ISO_C_BINDING . In the case of Intel Fortran, INTEGER is 4 bytes long, but in other implementations it is not a fact. Using a named constant guarantees portability.
Here are the C objects that might be available for Fortran application:
- Numeric types: integer, floating point, complex
- Logical types (there is a LOGICAL type in Fortran)
- Lines
- Structures
- Pointers
- Arrays
- Global variables
- Functions
At the same time, the Fortran types remain old, but we modify them using the interoperable KIND parameter and the constants from the module. This will serve as a kind of “link” between types C and Fortran. The correspondence between the types can be found in the following table:
| Fortran Type | KIND parameter | Type C |
|---|---|---|
| INTEGER | C_int | int signed int |
| C_SHORT | short int signed short int | |
| C_LONG | long int signed long int | |
| C_LONG_LONG | long long int signed long long int | |
| C_SIGNED_CHAR | signed char unsigned char | |
| C_SIZE_T | size_t | |
| REAL | C_FLOAT | float |
| C_DOUBLE | double | |
| C_LONG_DOUBLE | long double | |
| COMPLEX | C_COMPLEX | _Complex |
| C_DOUBLE_COMPLEX | double _Complex | |
| C_LONG_DOUBLE_COMPLEX | long double _Complex | |
| LOGICAL | C_BOOL | _Bool |
| Charter | C_char | char |
Another feature - boolean types are set to true / false differently in C and Fortran.
If C uses 0 for false and any number other than 0 for true, then in Fortran even numbers are false, odd numbers are true.
Do not forget to use the -fpscomp logicals option (/ fpscomp: logicals on Windows), which changes the rules as in C.
By the way, it connects implicitly if you use the -standard-semantics option (/ standard-semantics on Windows) - a highly recommended option when working with the Fortran 2003 standard.
Now let's see how everything works with an example. If we write in Fortran
INTEGER(KIND=C_LONG) :: I
That use of KIND = C_LONG guarantees that the variable I will not have problems with the type when used in the C code, and there it will be of type long int (according to the nameplate). With the built-in types, everything is simple - we are looking for the appropriate constant for KIND in the nameplate and the thing in the hat. By the way, since all this functionality is available as a module, we should connect it using the USE keyword :
USE, INTRINSIC :: ISO_C_BINDING
Thus, all constants for types from the module will be available to us. In order not to clog the namespace, it is recommended to limit the scope to only those types that we are really going to use, for example like this:
USE, INTRINSIC :: ISO_C_BINDING, ONLY C_LONG
In addition to the types themselves, there is also a BIND construct (it is not part of a module — part of the Fortran 2003 standard) that tells the Fortran compiler that the corresponding name is object C. Moreover, this can be done explicitly and implicitly. For example, we have such global variables in C:
int a_int;
long b_long;
And we want to use them correctly in our Fortran code, for example, in a module:
MODULE TEST_BINDING
USE ISO_C_BINDING
! неявный binding A_INT и a_int
INTEGER(C_INT), BIND(C) :: A_INT
! явный binding B и b_long
INTEGER(C_LONG) :: B
BIND(C, NAME=' b_long ') :: B
END MODULE TEST_BINDING
Such a "bunch" is needed for objects. In this example, we can work in the Fortran code with the same global variables created in C. If we have some function, for example
Cfunc(float a1, double a2);
Then you can use such data as arguments, and you do not need to do BIND :
REAL(C_FLOAT) :: A1
COMPLEX(C_DOUBLE) :: A2
One of Fortran and C's problems was the differences when working with strings. Therefore, in order to be able to pass a string to such a function:
void copy(char in[], char out[]);
You need to use KIND = C_CHAR and the line termination character C_NULL_CHAR (analogue \ 0 in Fortran):
CHARACTER(LEN=10, KIND=C_CHAR) :: DIGIT_STRING = C_CHAR '123456789' // C_NULL_CHAR
CHARACTER(KIND=C_CHAR) :: DIGIT_ARR(10)
And our line from Fortran will be great to be “friends” with C - you can safely pass it to a function!
But the function also needs to be somehow connected with its counterpart from C. This is done in Fortran using the interfaces:
INTERFACE
SUBROUTINE COPY(IN, OUT), BIND(C)
USE ISO_C_BINDING
CHAR(KIND=C_CHAR), DIMENSION(*) :: IN, OUT
END SUBROUTINE COPY
END INTERFACE
And now we can safely write
CALL COPY(DIGIT_STRING, DIGIT_ARR)
The most interesting thing is working with pointers. For a function with pointers as arguments
short func(double *a; int *b; int c[10]; void *d)
You can use the following variables in Fortran:
REAL(C_DOUBLE) :: A ! A соответствует *а, потому как в Фортране аргументы передаются по ссылке
INTEGER(C_INT) :: B, V(10) ! B и С соответствуют *b и c[]
TYPE(C_PTR), VALUE :: D !D соответствует *d, так как тип у указателя void*
In addition to the fact that there is a KIND parameter for pointers , there are also a number of additional features, for example, there is a "null" pointer C_NULL_PTR - an analogue of null from C. There are also special functions.
C_F_POINTER associates the Fortran pointer with object C. The syntax for this function is as follows:
CALL C_F_POINTER( CPTR, FPTR [,SHAPE] )
TYPE(C_PTR), INTENT(IN) :: CPTR
, POINTER, INTENT(OUT) :: FPTR
INTEGER, INTENT(IN), OPTIONAL :: SHAPE
As an input argument to CPTR, we pass a pointer to an object in C; at the output, we have a Fortran pointer FPTR to this object.
C_LOC returns the address of an object C or Fortran:
C_ADDRESS = C_LOC(OBJECT)
C_ASSOCIATED checks whether our pointer is set to null or not, as well as whether it is associated with object C.
Derived types also did not stand aside. For example, such a ctype structure
typedef struct
{
int a, b;
float c;
} ctype;
will work with type FTYPE:
TYPE, BIND(C) :: FTYPE
INTEGER(C_INT) :: A, B
REAL(C_FLOAT) :: C
END TYPE FTYPE
Of course, I won’t be able to describe in detail all the details of the standard within the framework of the post, but I did not set this goal, but I think that I shed light on how this whole thing works. Well, the final example, which is close to reality, which shows how the ISO_C_BINDING module can be used to call functions from both Fortran and C.
Let's start with the example of Fortran that calls the C function:
int C_Library_Function(void* sendbuf, int sendcount, int *recvcounts);
So, we create the interface with the necessary KIND parameters:
MODULE FTN_C_2
INTERFACE
INTEGER (C_INT) FUNCTION C_LIBRARY_FUNCTION (SENDBUF, SENDCOUNT, RECVCOUNTS) BIND(C, NAME='C_LIBRARY_FUNCTION’)
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
TYPE (C_PTR), VALUE :: SENDBUF
INTEGER (C_INT), VALUE :: SENDCOUNT
TYPE (C_PTR), VALUE :: RECVCOUNTS
END FUNCTION C_LIBRARY_FUNCTION
END INTERFACE
END MODULE FTN_C_2
And now the function call directly:
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_INT, C_FLOAT, C_LOC
USE FTN_C_2
...
REAL (C_FLOAT), TARGET :: SEND(100)
INTEGER (C_INT) :: SENDCOUNT
INTEGER (C_INT), ALLOCATABLE, TARGET :: RECVCOUNTS(100)
...
ALLOCATE( RECVCOUNTS(100) )
...
CALL C_LIBRARY_FUNCTION(C_LOC(SEND), SENDCOUNT, C_LOC(RECVCOUNTS))
...
And no problem with either names or data types.
On the contrary, if we have the task of calling some Fortran function (a very common task), then this module will also help in solving problems. If we have a Simulation function :
SUBROUTINE SIMULATION(ALPHA, BETA, GAMMA, DELTA, ARRAYS) BIND(C)
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
INTEGER (C_LONG), VALUE :: ALPHA
REAL (C_DOUBLE), INTENT(INOUT) :: BETA
INTEGER (C_LONG), INTENT(OUT) :: GAMMA
REAL (C_DOUBLE),DIMENSION(*),INTENT(IN) :: DELTA
TYPE, BIND(C) :: PASS
INTEGER (C_INT) :: LENC, LENF
TYPE (C_PTR) :: C, F
END TYPE PASS
TYPE (PASS), INTENT(INOUT) :: ARRAYS
REAL (C_FLOAT), ALLOCATABLE, TARGET, SAVE :: ETA(:)
REAL (C_FLOAT), POINTER :: C_ARRAY(:)
...
! ассоциируем C_ARRAY с массивом, выделенным в C
CALL C_F_POINTER (ARRAYS%C, C_ARRAY, (/ARRAYS%LENC/) )
...
! выделяем память под массив и делаем его доступным в С
ARRAYS%LENF = 100
ALLOCATE (ETA(ARRAYS%LENF))
ARRAYS%F = C_LOC(ETA)
...
END SUBROUTINE SIMULATION
We declare the structure in C:
struct pass {int lenc, lenf; float *c, *f;};
And the function:
void simulation(long alpha, double *beta, long *gamma, double delta[], struct pass *arrays);
And we can safely call her:
simulation(alpha, &beta, &gamma, delta, &arrays);
That's all. I think that the use of this feature of the new standard will allow many developers to avoid a large number of problems, and Fortran and C will be as friendly as ever.