Search code examples
fortrancythonlanguage-interoperability

Fortran C interface for program containing non-c-interoperable derived type


I have a large Fortran code base for which I am trying to write a Python interface. I decided I would go the Cython route. However, I have derived types that are not C-interoperable. As an example,

type :: SomeType
  real, allocatable :: not_interoperable
  [other members]
contains
  procedure :: init
end type

...

subroutine init(this) bind(c)
  class(SomeType), intent(inout) :: this
...
end subroutine init

I would be completely happy not being able to access the member not_interoperable from Python, and would actually prefer not to expose it to the user anyway. i.e. some python class that includes only the other members would be perfectly fine, as long as functions inside Fortran can still "see" them. Here, the compiler fails because init has a polymorphic dummy argument. I can remove init as a type-bound procedure, and then make the change class(SomeType) -> type(SomeType), but then I still get the error that this is a dummy argument to a bind(c) procedure but is not C interoperable because the derived type is not C interoperable.

Is there any way around this? The only solution I can see is removing all non-C-interoperable members out of SomeType and making them global/module variables in the Fortran code, but I'd rather avoid that. Basically, I want a way to hide variables from Python (like those that are not C interoperable) while still keeping the Fortran code modular/preventing global variables.


Some more details:

My program roughly works as:

type(SomeType) :: instance
instance%init() ! no input arguments, but reads a file
instance%do_some_stuff()
instance%do_stuff_with_args(...) ! args are all C-interoperable
instance%finalize()

I would like a similar workflow in Python

from FortranModule import SomeType
instance = SomeType(...) # does not read a file, variables passed directly
instance.do_stuff()
instance.member = 3
instance.do_stuff_with_args(...)
instance.finalize()

But in Python, not_interoperable is never accessed. I want my Fortran code to still have it and use it, and it is still affected by SomeType%init, but I do not want/need it in Python. In principle I guess I can hide it as a global variable which gets modified by this function, but then (a) I can only have one instance and (b) I don't like global variables as a general rule (general consensus is that it reduces modularity and maintainability).

Also, just for clarity: I have a lot of non-interoperable types, and they are not as simple as real, allocatable, so rewriting all of them is not really realistic.


Solution

  • AFAIK there's no way to interoperate with C a derived type containing allocatable component). A solution is to expose to C only an opaque handle (void* C pointer) to a derived type instance. Then there's the choice to keep the components completely hidden from C, or to give C some pointers to them.

    Illustration code:

    Fortran part:

    ! ===========================================
    ! file: FC_bind_handle_f.f90
    module somemodule
       use, intrinsic :: iso_c_binding
       implicit none
        
       ! c_float is needed only if one wants to 
       ! access %a directly from C
       type :: sometype
          integer(c_int), allocatable :: a(:)
       end type
        
    contains
    
       ! example routine that processes the content of the type
       subroutine sometype_x2_f(x)
          type(sometype), intent(inout) :: x
          
          x%a = 2 * x%a
       end subroutine
       
    end module
    ! ===========================================
    
    ! ===========================================
    ! file: somemodule_wrap.f90 
    module somemodule_wrap
       use, intrinsic :: iso_c_binding
       use somemodule 
       implicit none
    
    contains
    
       ! function that creates a sometype instance,
       ! allocates the %a component,
       ! and returns a C pointer to the instance 
       type(c_ptr) function sometype_create(len) bind(c)
          integer(c_int), value :: len
          
          type(sometype), pointer :: p
          
          allocate(p)
          allocate(p%a(len))
          sometype_create = c_loc(p)
       end function
    
       ! routine that releases a handle 
       subroutine sometype_free(ptr) bind(c)
          type(c_ptr) :: ptr
          
          type(sometype), pointer :: p
          
          call c_f_pointer(ptr,p)
          deallocate(p%a)   ! would actually be automatically deallocated
          deallocate(p)
          ptr = c_null_ptr
       end subroutine
       
       ! function that returns a C pointer to the %a component, 
       ! allowing to work directly on it from C
       type(c_ptr) function sometype_a_ptr(ptr, n) bind(c)
          type(c_ptr), value :: ptr
          integer(c_int), intent(out) :: n
          
          type(sometype), pointer :: p
          
          call c_f_pointer(ptr,p)
          n = size(p%a)
          sometype_a_ptr = c_loc(p%a)
       end function
    
       ! wrapper routine to a Fortran routine that performs 
       ! some processing on a sometype instance
       subroutine sometype_x2(ptr) bind(c)
          type(c_ptr), value :: ptr
        
          type(sometype), pointer :: p
        
          call c_f_pointer(ptr,p)
          call sometype_x2_f(p)
       end subroutine
       
    end module
    ! ===========================================
    

    C part:

    //===========================================
    //file: FC_bind_handle_c.c
    
    #include <stdio.h>
    
    void*  sometype_create(int len);
    void*  sometype_free(void** h);
    int*   sometype_a_ptr(void* h, int* n);
    void*  sometype_x2(void* h);
    
    int main(void) 
    {
        // creates an instance of sometype with 6 elements in %a
        void* h = sometype_create( 6 );
        
        // get a pointer to %a and fill it. Alternatively one could 
        // define a "put" routine to avoid any access to %a from C
        int n;
        int* a = sometype_a_ptr(h, &n);
        for (int i = 0; i < n; i++) {
            a[i] = i;
        }
        
        // print the content of the instance
        for (int i = 0; i < n; i++) printf("%d  ",a[i]);
        printf("\n\n");
        
        // double all the values
        sometype_x2(h);
        
        // print the content of the instance
        for (int i = 0; i < n; i++) printf("%d  ",a[i]);
        printf("\n\n");
    
        // releases the instance
        sometype_free(&h);
        
        return 0;
    }
    //===========================================
    
    

    Result:

    $ gfortran -c FC_bind_handle_f.f90 && gcc FC_bind_handle_c.c FC_bind_handle_f.o -lgfortran
    $ ./a.out
    0  1  2  3  4  5  
    
    0  2  4  6  8  10  
    

    However, managing C pointers from Python is maybe not convenient/possible (I don't know enough of python...). Then, instead of returning void* pointer handle, one can return identifiers, which refer to instances in a global array:

    ! ===========================================
    ! file: FC_bind_id_f.f90
    module somemodule
       use, intrinsic :: iso_c_binding
       implicit none
        
       ! c_float is needed only if one wants to 
       ! access %a directly from C
       type :: sometype
          integer(c_int), allocatable :: a(:)
       end type
        
    contains
    
       ! example routine that processes the content of the type
       subroutine sometype_x2_f(x)
          type(sometype), intent(inout) :: x
          
          x%a = 2 * x%a
       end subroutine
       
    end module
    ! ===========================================
    
    ! ===========================================
    ! file: somemodule_wrap.f90 
    module somemodule_wrap
       use, intrinsic :: iso_c_binding
       use somemodule 
       implicit none
       
       ! again, "target" is needed only if one wants to access the %a component from C
       type(sometype), allocatable, target :: h(:)
       logical, allocatable :: active(:)
    
    contains
    
       ! function that creates a sometype instance,
       ! allocates the %a component,
       ! and returns a C pointer to the instance 
       integer(c_int) function sometype_create(len) bind(c)
          integer(c_int), value :: len
          
          integer :: id
          
          if (.not.allocated(h)) then
             allocate(h(0))
             allocate(active(0))
          end if
          id = findloc(active,.false.,dim=1)
          if (id == 0) then
             h = [h, sometype()]
             active = [active, .false.]
             id = size(h)
          end if
          active(id) = .true.
          allocate(h(id)%a(len))
          sometype_create = id
       end function
    
       ! routine that releases a handle 
       subroutine sometype_free(id) bind(c)
          integer(c_int) :: id
                
          deallocate(h(id)%a)
          active(id) = .false.
          if (id == size(h)) then
             do while (id > 0 .and. .not.active(id))
                h = h(1:id-1)
                active = active(1:id-1)
                id = id-1
             end do
          end if
          id = 0
       end subroutine
       
       ! function that returns a C pointer to the %a component, 
       ! allowing to work directly on it from C
       type(c_ptr) function sometype_a_ptr(id, n) bind(c)
          integer(c_int), value :: id
          integer(c_int), intent(out) :: n
                
          n = size(h(id)%a)
          sometype_a_ptr = c_loc(h(id)%a)
       end function
    
       ! wrapper routine to a Fortran routine that performs 
       ! some processing on a sometype instance
       subroutine sometype_x2(id) bind(c)
          integer(c_int), value :: id
            
          call sometype_x2_f(h(id))
       end subroutine
       
    end module
    ! ===========================================
    

    C part:

    //===========================================
    //file: FC_bind_id_c.c
    
    #include <stdio.h>
    
    int    sometype_create(int len);
    void*  sometype_free(int* id);
    int*   sometype_a_ptr(int id, int* n);
    void*  sometype_x2(int id);
    
    int main(void) 
    {
        // creates an instance of sometype with 6 elements in %a
        int id = sometype_create( 6 );
        
        // get a pointer to %a and fill it. Alternatively one could 
        // define a "put" routine to avoid any access to %a from C
        int n;
        int* a = sometype_a_ptr(id, &n);
        for (int i = 0; i < n; i++) {
            a[i] = i;
        }
        
        // print the content of the instance
        for (int i = 0; i < n; i++) printf("%d  ",a[i]);
        printf("\n\n");
        
        // double all the values
        sometype_x2(id);
        
        // print the content of the instance
        for (int i = 0; i < n; i++) printf("%d  ",a[i]);
        printf("\n\n");
    
        // releases the instance
        sometype_free(&id);
        
        return 0;
    }
    //===========================================