How do I convert an array of structs into an array of derived type with ISO C BINDINGS?

219 Views Asked by At

I have the following test C library:

#include <stdlib.h>

struct mystruct {
    int a;
    double  b;
};

struct mystruct *return_array_of_structs(int *size);

struct mystruct *return_array_of_structs(int *size) {

    int i;
    struct mystruct *ptr;
    ptr = malloc(sizeof(struct mystruct)*10);
    
    for(i=0; i<10; i++) {
        ptr[i].a = i+1;
        ptr[i].b = (i+1)*1.0L;
    }
    
    *size=10;
    return ptr;
}

And the following module intended to be compiled with f2py:

module test_c_lib

        use iso_c_binding
        implicit none

        type :: t_mystruct
            integer :: a
            real(8) :: b
        end type
        
contains

        subroutine test()

                use iso_c_binding
                
                type(c_ptr)     :: ret_c_ptr            
                integer         :: length
                
                ! Interface to C function
                interface                        
                        type(c_ptr) function c_return_array_of_structs(a) bind(C, name="return_array_of_structs")
                                import
                                integer(c_int) :: a
                        end function
                end interface

                ! Call C function               
                ret_c_ptr = c_return_array_of_structs(length)

        
        end subroutine

end module

The following makefile compiles this:

f_mod.so:       f_mod.f90 c_lib.o
                f2py -c f_mod.f90 c_lib.o -m f_mod

c_lib.o:        c_lib.c
                gcc -c -fpic c_lib.c -o c_lib.o

I can load the library in Python normally and execute the test() subroutine without problems.

import f_mod
f_mod.test_c_lib.test()

But I do not how to convert ret_c_ptr into an array of derived type t_mystruct that I can normally operate as an array in Fortran. Any hint? Note: the question is related to iso c bindings and Fortran, not to f2py or its integration with Python.

2

There are 2 best solutions below

2
francescalus On BEST ANSWER

If you have a C pointer and want to associate a Fortran pointer with the target of that C pointer, you want to use c_f_pointer to do that association. c_f_pointer doesn't (generally) care whether the type is an intrinsic one or not, or scalar or not.

Let's define the Fortran derived type as an interoperable1 one:

type, bind(c) :: t_mystruct
  integer(c_int) :: a
  real(c_double) :: b
end type

and then have a Fortran pointer array of that type:

type(t_mystruct), pointer :: ptr_f(:)

With ptr_c pointing to a suitable lump of memory we can do the association:

call c_f_pointer(ptr_c, ptr_f, [length])

to make ptr_f an array of shape [length] pointing to the C target.


1 Being interoperable isn't necessary, but it's certainly helpful to be if it can be.

0
M.E. On

The right answer to this question is the one provided by francescalus above, however I include the complete excerpt of code with the functions exposed by f2py to Python.

f2py will not allow to return numpy arrays of derived types (which would be a numpy vector of a custom dtype), so you need to return individual vectors per field. Also f2py can not return allocatable arrays without explicit size, therefore this function needs to be split into two calls, one to get the length and another one to get the actual array.

While these are clear limitations of f2py this allows to call directly Fortran functions (or C encapsulated functions as in this example) and get numpy arrays as return values.

The code is not optimised as it implies invoking the C function twice, maybe there is the possibility to use sort kind of static variable like in C, so the second invoke would already had computed the values, although I do not know:

  1. if that is possible at all in Fortran
  2. if that is possible within the context of a shared object (which is what f2py creates from the module)

As there is not that many examples around f2py this might be useful for someone:

    module test_c_lib
    
            use iso_c_binding
            implicit none
            
            type, bind(c)  :: t_mystruct
                    integer(c_int) :: a
                    real(c_double) :: b
            end type
            
    contains

        subroutine get_array_len(l)

                use iso_c_binding
                
                integer, intent(out)    :: l
                
                type(c_ptr)               :: ret_c_ptr            
                integer                   :: length
                
                ! Interface to C function
                interface                        
                        type(c_ptr) function c_return_array_of_structs(a) bind(C, name="return_array_of_structs")
                                import
                                integer(c_int) :: a
                        end function
                end interface

                ! Call C function               
                ret_c_ptr = c_return_array_of_structs(length)
                l = length
        
        end subroutine

        subroutine get_array(l, a, b)

                use iso_c_binding
                
                integer, intent(in)     :: l
                integer, intent(out)    :: a(l)
                real(8), intent(out)    :: b(l)         
                
                type(c_ptr)               :: ret_c_ptr            
                type(t_mystruct), pointer :: f_ptr(:)
                integer                   :: length
                
                ! Interface to C function
                interface                        
                        type(c_ptr) function c_return_array_of_structs(a) bind(C, name="return_array_of_structs")
                                import
                                integer(c_int) :: a
                        end function
                end interface

                ! Call C function               
                ret_c_ptr = c_return_array_of_structs(length)
                call c_f_pointer(ret_c_ptr, f_ptr, [length])
                a = f_ptr%a
                b = f_ptr%b
        
        end subroutine

end module