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:
- It could simply return the
type(c_ptr)
and let the userc_f_pointer
it back to the c-interoperable user-defined type (HereUserData
, for the example), but that forces the use ofiso_c_binding
by the user and I would prefer to avoid it. - I do not know any way to inform the retrieval function of the expected user-defined type so that it could do the manipulation described above automatically and directly return a pointer to the user-defined type.
- As the user-pointer is an arbitrary derived-type, the retrieval function could return an unlimited polymorphic pointer
class(*), pointer
, but this cannot be used withc_f_pointer
with thetype(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:
- Is that a correct (will it work for every compiler?) and safe way to proceed, and the limitation of
c_f_pointer
withclass(*)
can be bypassed that way using pointers? - Can this be made pure Fortran 2003, as mentioned above (replacing the use of
type(*)
?) - Can this be further extended in order to get rid of the
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) - Am I missing anything else?
Thank you for your help.
Gaétan JALIN is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.