cfortranfortran2003opaque-pointers

How to call C functions involving opaque pointers from Fortran 2003 correctly?


I'm learning Fortran 2003. As a training task, I'm trying to make calls from Fortran 2003 to a C library that uses opaque pointers:

struct foobar_s;
typedef struct foobar_s *foobar;
foobar foo_create(enum foo, unsigned int);
void foo_destroy(foobar);

Most advice I found on the internet tells me to describe the foobar type as type(c_ptr), so the following is supposed to work:

!foobar foo_create(enum foo, unsigned int);
function foo_create(mode,n) bind(c) ret(foo)
 type(c_ptr) :: foo
 integer(kind(ENUM_FOO_CONSTANT)), value :: mode
 integer(kind=c_int), value :: n
end function

This declares foo_create as returning a void* instead of foobar = struct foobar_s *, but it works on modern architectures anyway.

I have been trying to create a distinct Fortran type, closer to the intent of opaque C pointer. The only thing that worked for me was:

type, bind(c) :: foobar
  private
  type(c_ptr) :: ptr
end type

which corresponds to:

typedef struct {
    void * ptr;
} foobar;

on the C side. Now, §6.7.2.1 of C standard guarantees that the address of the beginning of a struct is the address of the first element (right?) but there may be some padding at the end of it (but on architectures I use there is not, because pointers are self-aligned), so this whole contraption works on my machines:

!foobar foo_create(enum foo, unsigned int);
function foo_create(mode,n) bind(c) ret(foo)
 type(foobar) :: foo
 integer(kind(ENUM_FOO_CONSTANT)), value :: mode
 integer(kind=c_int), value :: n
end function

!void foo_destroy(foobar);
 sobroutine foo_destroy(foo) bind(c)
 type(foobar), value :: foo
end subroutine

I have verified that Valgrind shows no errors for a program that calls C functions foo_create() and foo_destroy() from Fortran using this type definition. Still, it cannot be a definite proof.

Is the assumption that struct { void * ptr } has the same size and bit-pattern as struct foobar_s * going to break? Is this the best way to wrap an opaque C pointer (and create a distinct type) in Fortran 2003?


Solution

  • The C language requires that all declarations that refer to the same object or function have compatible type. Given the effective C declarations of your Fortran code, your approach breaks that requirement. A practical outcome of that requirement is that a compiler could use different approaches to return something declared struct { void * ptr }, than something declared struct foobar_s * (e.g the aggregate might be returned in an area denominated by a hidden argument passed to the function, a pointer result might be returned in a register). Such a difference in implementation would be catastrophic for your code.

    TYPE(C_PTR) in Fortran can be used for both void * and struct foobar_s*, there is an implied requirement on a companion C compiler to a Fortran processor that the same representation method be used for all C object pointer types (see f2003 note 15.9).

    A typical approach is to write small Fortran wrapper procedures around the C functions, that appropriate set and reference the private C_PTR component. The Fortran type with the C_PTR component does not need to be interoperable. If the Fortran type is not interoperable, you can use modern Fortran features like type extension and finalizers - foo_destroy looks like something that would be useful to call from a finalizer.

    MODULE Fortran_Wrapper
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: xxxxx
      ...
      ! Enum definition in here somewhere.
      ...
      PUBLIC :: foobar
      PUBLIC :: Create
    
      ! Wrapper for a pointer to foobar_s.
      TYPE :: foobar
        PRIVATE
        TYPE(C_PTR) :: ptr = C_NULL_PTR
      CONTAINS
        FINAL :: final
      END TYPE foobar
      ...
    CONTAINS
      ! Wrapper around foo_create, exposed to Fortran client code.
      FUNCTION Create(mode, n) RESULT(obj)
        INTEGER(KIND(ENUM_FOO_CONSTANT)), INTENT(IN) :: mode
        ! Perhaps the next argument is taken as default integer, and 
        ! you do kind conversion inside this wrapper.
        INTEGER(C_INT), INTENT(IN) :: n
        TYPE(foobar) :: obj
    
        INTERFACE
          FUNCTION foo_create(mode, n) BIND(C, NAME='foo_create')
            USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_INT, C_PTR
            IMPORT :: ENUM_FOO_CONSTANT
            IMPLICIT NONE
            INTEGER(KIND(ENUM_FOO_CONSTANT)), VALUE :: mode
            INTEGER(KIND=C_INT), VALUE :: n
            TYPE(C_PTR) :: foo_create
          END FUNCTION foo_create
        END INTERFACE
    
        obj%ptr = foo_create(mode, n)
      END FUNCTION Create
    
      ! Use a finalizer to do automatic cleanup off the C structures.
      ! (Impure elemental is F2008.)
      IMPURE ELEMENTAL SUBROUTINE final(obj)
        TYPE(foobar), INTENT(INOUT) :: obj
        INTERFACE
          SUBROUTINE foo_destroy(obj) BIND(C, NAME='foo_destroy')
            USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
            IMPLICIT NONE
            TYPE(C_PTR), VALUE :: obj
          END SUBROUTINE foo_destroy
        END INTERFACE
    
        IF (C_ASSOCIATED(obj%ptr)) CALL foo_destroy(obj%ptr)
      END SUBROUTINE final 
    END MODULE Fortran_Wrapper
    

    (Note that the Fortran interface body in the question is missing the VALUE attribute on the two dummy argument definitions, otherwise the corresponding C prototype is foobar foo_create(enum foo*, unsigned int*).)