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:
- Restrictions on data types that can 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. 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:
- Numeric types: integer, floating point, complex
- Boolean types (there are LOGICAL types in Fortran)
- Strings
- Structures
- Pointers
- Arrays
- Global variables
- Functions
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 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 |
---|
CHARACTER
| C_CHAR | char |
---|
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.