I have a C code (I am not the owner of said code) that generates the following structures
struct Vector
{
int size;
double data[3];
};
struct VerticesVect
{
int size;
struct Vector* data;
};
And so on : there are a lot of additional constructs (typically structures of the previously defined structure).
I need to call said C code/library from a Fortran code. The Fortran code will use the previously C-generated structures. The Fortran code won't write / edit them in any way, it just needs them as input.
I am trying to figure out a way to declare these types in Fortran so that I can use them.
use, intrinsic :: ISO_C_BINDING
followed by
type, bind(c) :: Vector
integer(kind=C_INT32_T) :: size
real(kind=C_DOUBLE) :: data(3)
end type Vector
is obviously fine.
type, bind(c) :: VerticesVect
integer(kind=c_int) :: size
type(Vector), dimension(:) :: data
end type VerticesVect
is not valid though ("data must have an explicit shape"), nor
type, bind(c) :: VerticesVect
integer(kind=c_int) :: size
type(Vector), pointer :: data
end type VerticesVect
("Component 'data' cannot have the POINTER attribute because it is a member of the BIND(C) derived type 'verticesvect'")
nor seems to be valid any combination of allocatable, pointer, positioning of (:) that I could think of.
Unfotunately, specifying a static size such that it is big enough to cover all cases is not an acceptable solution, as the "compounded" maximum sizes of all subsequent constructions end up leading to an unreasonably large total object.
Is there a way to write these declarations such that the compiler accepts it and everything maps correctly ? Can/should i use the c_ptr
type ? Can/should I get rid of the bind(c)
?
Thanks for your help
This ...
type(Vector), dimension(:) :: data
... attempts to declare member data
as an assumed-shape array. This is wrong in the C sense that C pointers are not arrays. It is also wrong in the C-binding sense that interoperable defined types are explicitly forbidden from having members that are assumed-size or assumed-shape arrays.
This ...
type(Vector), pointer :: data
... seems more logical, but it is also incorrect because Fortran pointers and C pointers, though similar in concept, are not wholly analogous. The C-binding maps C pointers to type type(c_ptr)
. There are some caveats wrapped around that, but we don't need to dig into those here.
Thus, the way to define an interoperable user-defined type corresponding to a C structure with a pointer member is to use type(c_ptr)
for the corresponding member on the Fortran side and to give the type the bind(c)
attribute:
type, bind(c) :: VerticesVect
integer(kind=c_int) :: size
type(c_ptr) :: data
end type VerticesVect
Additionally, you will want to be able to access the Vector
objects from the Fortran side. For that, you will probably want to engage ISO_C_BINDING
's c_f_pointer()
function. This is the main interoperable bridge between C pointers and Fortran pointers. For example:
type(VerticesVect) :: vertices
! ... assign an appropriate value to 'vertices' ...
type(Vector), dimension(:), pointer :: vectors
call c_f_pointer(vertices%data, vectors, (/vertices%size/))
! ... use 'vectors' to access the data ...
Although you say you need only read access, both read and write access should work fine.
The following demo compiles successfully and works as expected for me:
main.f90
program vectors
use, intrinsic :: iso_c_binding
implicit none
type, bind(c) :: Vector
integer(kind=c_int) :: size
real(kind=c_double) :: data(3)
end type Vector
type, bind(c) :: VerticesVect
integer(kind=c_int) :: size
type(c_ptr) :: data
end type VerticesVect
interface
function generate3DVertices() bind(C, name='generate3DVertices')
use, intrinsic :: iso_c_binding
type(c_ptr) :: generate3DVertices
end function generate3DVertices
end interface
type(VerticesVect), pointer :: vertices
type(c_ptr) verticesPtr
verticesPtr = generate3DVertices()
call c_f_pointer(verticesPtr, vertices)
call write_vectors(vertices)
contains
subroutine write_vectors(vertices)
type(VerticesVect), intent(in) :: vertices
type(Vector), dimension(:), pointer :: vectors
integer :: i
call c_f_pointer(vertices%data, vectors, (/vertices%size/))
write(6, *) 'Fortran vectors:'
do i = 1, vertices%size
write(6, '(3f10.6)') vectors(i)%data
end do
end subroutine write_vectors
end program
vect.c
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#define NUM_VERTICES 3
struct Vector {
int size;
double data[3];
};
struct VerticesVect {
int size;
struct Vector* data;
};
struct VerticesVect *generate3DVertices(void) {
struct VerticesVect *vertices = malloc(sizeof *vertices);
if (!vertices) {
fprintf(stderr, "Memory allocation failed\n");
exit(1);
}
vertices->size = NUM_VERTICES;
vertices->data = calloc(NUM_VERTICES, sizeof *vertices->data);
if (!vertices->data) {
fprintf(stderr, "Memory allocation failed\n");
exit(1);
}
puts(" C vectors:");
srand(time(NULL));
for (int i = 0; i < NUM_VERTICES; i++) {
for (int j = 0; j < 3; j++) {
vertices->data[i].data[j] = rand() / (double) RAND_MAX;
}
printf("%10.6f%10.6f%10.6f\n",
vertices->data[i].data[0], vertices->data[i].data[1], vertices->data[i].data[2]);
}
putchar('\n');
fflush(stdout);
return vertices;
}
Makefile
OBJ = main.o vect.o
FC = gfortran
all: demo
demo: $(OBJ)
$(FC) $(LDFLAGS) -o $@ $^
%.o: %.f90
$(FC) $(FCFLAGS) -c -o $@ $<
The program calls calls a C function to generate random vectors and print C's interpretation of them, then prints the Fortran interpretation of the same. When I run it, the vectors printed by C are identical to the ones printed by Fortran (to the 6-digit precision used on both sides).