📜 ⬆️ ⬇️

Fortran 2003: teach Fortran and C to be friends


In the language of Fortran written a huge amount of code, debugged and worked for many years. I'm not going to raise the question "which is better - Fortran or C?". Each language has its strengths and weaknesses. But, given the large distribution 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 the Fortran language, and the other - in C. Here only these languages ​​have certain specificity, about which I partially mentioned , and in order for the application written by us to work correctly, you need to take into account many nuances. Differences in data types, calling conventions (calling conventions), naming conventions make the task of creating a mixed language application far nontrivial. It is good that in Fortran 2003 there appeared a whole set of tools specially developed for solving the problem of interoperability of C and Fortran. By the way, I don’t remember other languages ​​that would standardize this kind of work - one more “plus” to Fortran for the outstretched “hand of friendship”.

What is this inter-opera and so on? The term “interoperability” means the possibility of calling the function C in the 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 match in C. The main idea is that everything should work the same in C, as well as in Fortran. It is worth noting that C means standard C99 (ISO / IEC 9899: 1999). By the way, the concrete implementation of the Fortran compiler has the right to choose which of the C compilers to be friends with. In the case of Intel Fortan, this 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 (as one would expect).

Fortran implements support for the very interoperability through the following means:


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. Yes, and with the basic types are 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.
In order for all this to work well, a module appeared in Fortran that is 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 the code in C.
For example, for the int type we can use the INTEGER (C_INT) type in Fortran, and C_INT is defined in the ISO_C_BINDING module. In the case of Intel Fortran, the length of the INTEGER is 4 bytes, but in other implementations it is not a fact. Using a named constant ensures portability.
')
Here are the C objects that may be available to the application in Fortran:


At the same time, the Fortran types remain old, but we modify them with the help of the interoperable parameter KIND and 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 TypeKIND parameterType C
INTEGERC_INTint
signed int
C_SHORTshort int
signed short int
C_LONGlong int
signed long int
C_LONG_LONGlong long int
signed long long int
C_SIGNED_CHARsigned char
unsigned char
C_SIZE_Tsize_t
REALC_FLOATfloat
C_DOUBLEdouble
C_LONG_DOUBLElong double
COMPLEXC_COMPLEX_Complex
C_DOUBLE_COMPLEXdouble _Complex
C_LONG_DOUBLE_COMPLEXlong double _Complex
LOGICALC_BOOL_Bool
CHARACTER
C_CHARchar
I want to note that the unsigned int type is not supported in Fortran.
Another feature is that logical 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 option -fpscomp logicals (/ fpscomp: logicals on Windows), which changes the rules as in C.
By the way, it connects implicitly if using the option-standard-semantics (/ standard-semantics on Windows) is 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 

The use of KIND = C_LONG guarantees that the variable I will not have problems with the type when it is used in the C code, and there it will be of the type long int (in accordance with the table). With built-in types, everything is simple - we are looking for the corresponding constant for the KIND in the tablet and it's in the bag . By the way, since all this functionality is available as a module, then we should connect it using the USE keyword:
 USE, INTRINSIC :: ISO_C_BINDING 

Thus, we will have access to all constants for types from the module. In order not to clutter up the namespace, it is recommended to limit the scope to only those types that we are actually going to use, like this:
 USE, INTRINSIC :: ISO_C_BINDING, ONLY C_LONG 

In addition to the types themselves, there is also a BIND construct (not part of the module — part of the Fortran 2003 standard), which tells the Fortran compiler that the corresponding name is an object C. And it 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 

This "bundle" is needed for objects. In this example, we will be able to work in Fortran code with the same global variables created in C. If we have a certain function, for example
 Cfunc(float a1, double a2); 

You can use such data as arguments, and you don’t need to do BIND :
 REAL(C_FLOAT) :: A1 COMPLEX(C_DOUBLE) :: A2 

One of the problems of FORTRAN and C was the differences when working with strings. Therefore, in order to pass a line to such a function:
 void copy(char in[], char out[]); 

You need to use KIND = C_CHAR and the character of the end of the line C_NULL_CHAR (analogue of \ 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 to the function!
But the function also needs to be somehow connected with its counterpart from C. This is done in Fortran using 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 boldly write
 CALL COPY(DIGIT_STRING, DIGIT_ARR) 

The most interesting thing is working with pointers. For 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* 


Besides the fact that there is a KIND parameter for pointers , there is also a number of additional features, for example, there is a “null” pointer C_NULL_PTR , an analogue of the null from C. There are also special functions available.

C_F_POINTER associates a Fortran pointer with object C. The syntax of this function is as follows:
 CALL C_F_POINTER( CPTR, FPTR [,SHAPE] ) TYPE(C_PTR), INTENT(IN) :: CPTR <type_spec>, POINTER, INTENT(OUT) :: FPTR INTEGER, INTENT(IN), OPTIONAL :: SHAPE 

As an input argument to the CPTR, we pass a pointer to an object in C, and at the output we have a Fortran pointer FPTR to this object.

C_LOC returns the address of the object C or Fortran:
 C_ADDRESS = C_LOC(OBJECT) 


C_ASSOCIATED checks whether our pointer is null or not, and 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 the FTYPE type:
 TYPE, BIND(C) :: FTYPE INTEGER(C_INT) :: A, B REAL(C_FLOAT) :: C END TYPE FTYPE 


Of course, to describe in detail all the details of the standard within the framework of the post will not work, but I did not set this goal, but I think that I shed light on how this whole thing works. Well, the final example, 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, which calls the C function:
 int C_Library_Function(void* sendbuf, int sendcount, int *recvcounts); 

So, we create an 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 itself:
 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 problems with the names or data types.
On the contrary, if we have the task of calling some Fortran function (a very frequent task), then this module will also help in solving problems here. If we have the 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 a structure in C:
 struct pass {int lenc, lenf; float *c, *f;}; 

And function:
 void simulation(long alpha, double *beta, long *gamma, double delta[], struct pass *arrays); 

And we can safely call it:
 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 more friendly than ever.

Source: https://habr.com/ru/post/255305/


All Articles