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 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
    Charter
    C_charchar
    I want to note that in Fortran unsigned int type is not supported .
    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.

    Also popular now: