fortranvoid-pointerslanguage-interoperability

Passing and retrieving arbitrary data through a C void* from Fortran


I apologise in advance for the very long message.

I am seeking expertise in order to determine if what I am doing is safe/correct, or if alternatives exists.

I have a C library that can hold a user-pointer (void*) to arbitrary C data, so that the user can set and retrieve this pointer through the library and perform appropriate casting back to the original user-defined type in order to access the data. Imagine the following interface for simplification:

void set_pointer(void* user_ptr);
void* get_pointer();

I want to make a Fortran interface that allows the user to set this void* to arbitrary (C-interoperable) Fortran derived-types through Fortran as well.

The interface to the C code is relatively straight-forward using the iso_c_binding intrinsic module:

interface
    subroutine c_set_pointer(usr_ptr) bind(C, name="set_pointer")
        use, intrinsic :: iso_c_binding
        implicit none
        type(c_ptr), value, intent(in) :: usr_ptr
    end subroutine c_set_pointer
end interface

interface
    function c_get_pointer() result(usr_ptr) bind(C, name="get_pointer")
        use, intrinsic :: iso_c_binding
        implicit none
        type(c_ptr) :: usr_ptr
    end function c_get_pointer
end interface

Now imagine I have an arbitrary Fortran derived type (C-interoperable):

type, bind(C) :: UserData
    integer :: i
    real :: f
end type UserData

Now, setting the pointer is not so difficult, I guess, using assumed-type and c_loc:

subroutine set_pointer(data)
    use, intrinsic :: iso_c_binding
    implicit none
    type(*), target, intent(in) :: data

    call c_set_pointer(c_loc(data))
end subroutine set_pointer

Note I could use assumed-type directly in the interface of c_set_pointer and bypass the Fortran wrapper, but my goal would also be (if even possible) to make this pure Fortran 2003, and assumed-type is 2018 (I think).

It gets complicated when trying to retrieve the user-pointer as there is no Fortran equivalent to void* (to my knowledge), and the data could be any derived-type:

I did find a way to bypass the last point by associating the C pointer to a "byte array?":

function get_pointer() result(usr_ptr)
    use, intrinsic :: iso_c_binding
    implicit none
    class(*), pointer :: usr_ptr
    type(c_ptr) :: cptr
    character(len=:), pointer :: data

    cptr = c_get_pointer()
    call c_f_pointer(cptr, data) ! This seems to correctly associate the "byte array" to the user-pointer address
    print *, len(data) ! This gives a 0-length character (?)
    print *, c_loc(data) ! This gives the correct memory address of the user-pointer
                         ! But c_loc will fail if "data" is explicitly "len=0",
                         ! but works with "len=:" although len(data) gives 0 (?)
    usr_ptr => data
end function get_pointer

I used Apple clang 15.0.0 (1500.3.9.4) on a Mac M1 to test that code, and it seems to give the expected result, as I was able to correctly access the components of the user-defined type. It also worked with gfortran 13.2.0 (Still on Mac M1).

implicit none
type(UserData) :: data
type(UserData), pointer :: data_ptr
data = UserData(f = 3.14, i = 42)

call set_pointer(data)
data_ptr => get_pointer()
print *, data_ptr%f ! prints "3.14"
print *, data_ptr%i ! prints "42"

My question(s) is then:

Thank you for your help.


Solution

  • After looking at the comments and diving a little deeper in the standard, I realised that it was probably not worth being so much on the edge of the standard and simply return a type(c_ptr) from get_pointer().

    Thanks everyone for your help and knowledge!