user2769423
user2769423

Reputation: 11

How to pass derived type in C which is NOT interop with C struct?

I am trying to get Fortan API to work with the C code. The Fortran code contains mainly derived types which are NOT interoperable with the C by using iso_c_binding module. I have found this post that contains similar problem https://software.intel.com/en-us/forums/topic/394312. This post c-fortran interoperability - derived types with pointers suggest that there is new standard that could solve this problem, but it is still not implemented by any fortran compiler. I have tried this code with gfortran 4.8.3 and gcc:

module types
    type SomeDerivedType
            integer :: someInteger
            character*24 :: string
            real, dimension(:), allocatable :: array
    end type
end module

! Given the address of a C_PTR this subroutine allocates the memory required
! for a SomeDerivedType derived data type, initializes it with the given  values,
! and stores the C_LOCated address in the C_PTR.
subroutine makeDerivedType_(cdt) bind(c, name='makeDerivedType_')
    use iso_c_binding
    use types
    type (C_PTR) :: cdt
    type (SomeDerivedType), pointer :: fdt
    integer :: i
    allocate(fdt)
    allocate(fdt%array(0:10))
    fdt%someInteger = 4
    fdt%string = 'Hello'
    do i = 0,9
       fdt%array(i) = i*2.0
    end do
    cdt = C_LOC(fdt)
end subroutine makeDerivedType_

! This subroutine converts the given C_PTR value to a fortran pointer making
! it accessible again from fortran
subroutine examineDerivedType_(this) bind(c, name='examineDerivedType_')
    use iso_c_binding
    use types
    type(C_PTR), value :: this
    type(SomeDerivedType), pointer :: that
    integer :: i
    call C_F_POINTER(this,that)
    write(*,*) "that%someInteger", that%someInteger
    do i = 0,9
            print*, "that%array:", that%array(i)
    end do
 end subroutine examineDerivedType_

The corresponding C code is:

#include<stdio.h>
extern void makeDerivedType_(void **m);
extern void examineDerivedType_( void *m );
typedef struct SomeDerivedType {
    int someInteger;
    char string[24];
    double *array;
}SomeDerivedType;
int main(int argc, char **argv)
{
    void *m = 0;
    char *teststring[2] = {"test1", "test2"};
    makeDerivedType_(&m);
    SomeDerivedType* dt = (SomeDerivedType*) m;
    for(int i  = 0; i < 10; i++)
        printf("%f\n",dt->array[i]);
    printf("someInteger: %i\n",dt->someInteger); 
    printf("%s \n", dt->string);
    examineDerivedType_(m);
}

This program returns:

2.000000
8192.001968
524288.126953
8388610.039062
67108880.375000
0.000000
0.000000
0.000000
0.000000
0.000000
someInteger: 4
Hello                   ? 
 that%someInteger           4
 that%array:   0.00000000    
 that%array:   2.00000000    
 that%array:   4.00000000    
 that%array:   6.00000000    
 that%array:   8.00000000    
 that%array:   10.0000000    
 that%array:   12.0000000    
 that%array:   14.0000000    
 that%array:   16.0000000    
 that%array:   18.0000000 

It is interesting to see that string and someInteger variables are properly returned from C, while array returns bad values. I assumed that the reason is that array is not contiguously allocated in Fortran code so C returns contiguous memory locations starting from *(array+i). Is it possible to ensure that Fortran allocates array contiguously or exists any other more portable way to access derived type with allocatable components from C.

Upvotes: 1

Views: 1438

Answers (1)

The ultimate problem is just that your Fortran code uses real and the C code uses a double. If you make it compatible, it will work in this simple case.


real, dimension(:), allocatable :: array is NOT equivalent to a C pointer.

There is actually an array descriptor present in the object. The descriptor than contains a address (C style pointer) to the contiguously allocated array. The problem would be apparent if you used a different order of the components (e.g., the array first) or if there were additional components after the array.

You can read more about the implementation of the descriptor for one particular compiler in Handling Fortran Array Descriptors.

The offset of the address in the descriptor will typically be 0. That means that your code, even if incorrect, could actually work for this simple access, but you must also count with the padding in the derived type/struct.

In any way, your C struct must be more complicated and must include another struct - the descriptor. You should be careful to get the size of the struct right. The Intel's manual suggests it is 36 bytes for 32bit and 72 bytes for 64bit binaries for one-dimensional arrays.

Your link to the other question and my answer there suggests to use TS 29113. Intel's descriptor should be compatible this one and that's why the Intel's manual is useful. gfortran, however, is not yet compatible and uses a different descriptor.

Upvotes: 4

Related Questions