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

460 Views Asked by At

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?

1

There are 1 best solutions below

1
On BEST ANSWER

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*).)