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:
type(c_ptr)
and let the user c_f_pointer
it back to the c-interoperable user-defined type (Here UserData
, for the example), but that forces the use of iso_c_binding
by the user and I would prefer to avoid it.class(*), pointer
, but this cannot be used with c_f_pointer
with the type(c_ptr)
returned by the C interface.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:
c_f_pointer
with class(*)
can be bypassed that way using pointers?type(*)
?)bind(C)
limitation on the user-defined type? (Although I don't think so, as that would allow the use of allocatable arrays and such, and the size of such arrays would be lost)Thank you for your help.
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!