byrdman1982
byrdman1982

Reputation: 127

Passing Fortran derived type array to a C function as a struct

I have a Fortran derived type with a matching C struct. I am creating an array of the derived type, and I want to pass this array to a C function. Here is my Fortran code

module tmod

use iso_c_binding

integer, parameter :: nstring = 22
integer, parameter :: ntype = 3
!! Type with a bind(c)
type, bind(c) :: mystruct
  character (c_char) :: hello (nstring)
  character (c_char) :: world (nstring)
  integer (c_int) :: ii
end type mystruct

interface !! Function on the C side
  integer (c_int) function testfunc (t, asize) bind(C, name="testfunc")
   use iso_c_binding
   import mystruct
    type (mystruct) :: t (:) !< array of struct/derived type
    integer (c_int), value :: asize !< Size of the array
  end function testfunc
end interface

end module tmod


program testprog

use tmod
use iso_c_binding

type(mystruct), target :: t (0:ntype-1) !< Array of derived type set up with C-like indicies
integer (c_int) :: i
!! Initialize the struct array
do j = 0,ntype-1
  t(j)%hello = "HelLo"//c_null_char
  t(j)%world = "World"//c_null_char
  t(j)%ii = j-3
enddo
!! Call C function
i = testfunc(t, ntype)
end program testprog

My C code loops through the struct and prints the integer

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int const nstring = 22; /* matches string size from fortran */
struct mystruct{
  char hello[22];
  char world[22];
  int ii ;
}mystruct;

int testfunc (struct mystruct * t[], int asize ) {
/* Loop through array and print index then ii value */
  for (int j = 0; j < asize ; j++) {
          printf ("%d \n", j);
          printf ("%d \n", t[j]->ii);
  }
  return 0;
}

These compile with icc version 2021.5.0 and ifort version 2021.5.0 or gcc version 11.2.0 (this drops a note about declared here for mystruct t[]). It prints out the result of the first loop. It gives me a segmentation fault on the second pass through the loop. I can't pass the array by value because it is an array and that's apparently not allowed.
If I define my C function without the pointer

int testfunc (struct mystruct t[], int asize ) 

the program runs to the end, but does not print the correct information. How can I pass this Fortran array to the C function?

Upvotes: 3

Views: 534

Answers (1)

francescalus
francescalus

Reputation: 32451

Your Fortran interface has an assumed-shape dummy argument t. The interface for testfunc with this assumed-shape array is not interoperable with the formal parameter struct mystruct * t[] of the C function. An interoperable C function must have the argument associated with a pointer to CFI_cdesc_t object.

That said, there's little reason in the code you show to want to use an assumed-shape dummy argument, seeing as you are also passing the size of the array. Instead use an explicit-shape argument:

interface !! Function on the C side
  integer (c_int) function testfunc (t, asize) bind(C, name="testfunc")
   use iso_c_binding
   import mystruct
    integer (c_int), value :: asize !< Size of the array
    type (mystruct) :: t (asize) !< array of struct/derived type
  end function testfunc
end interface

Which is then going to lead to the next problem: you don't want struct mystruct * t[] but struct mystruct *:

int testfunc (struct mystruct *t, int asize ) {
/* Loop through array and print index then ii value */
  for (int j = 0; j < asize ; j++) {
          printf ("%d \n", j);
          printf ("%d \n", t[j].ii);
  }
  return 0;
}

Alternatively, if for some reason you do need the assumed-shape argument:

interface !! Function on the C side
  integer (c_int) function testfunc (t) bind(C, name="testfunc")
   use iso_c_binding
   import mystruct
    type (mystruct) :: t (:) !< array of struct/derived type
  end function testfunc
end interface

you can make that interoperable with

int testfunc (CFI_cdesc_t* t) {
/* Loop through array and print index then ii value */
  struct mystruct *address;
  CFI_index_t subscript[1];
  for (int j = 0; j < t->dim[0].extent ; j++) {
    subscript[0] = j;
    printf ("%d \n", j);
    address = (struct mystruct *) CFI_address(t, subscript);
    printf ("%d \n", address->ii);
  }
  return 0;
}

Either way, you'll probably want to address the assignments like

  t(j)%hello = "HelLo"//c_null_char

which sets the array t(j)%hello to 22 elements of "H". You'll want the right-hand side to itself be an array of 22 elements, say:

  t(j)%hello = [character(c_char) :: "H", "e", "l", "L", "o", c_null_char, [(" ", i=1,16)]]

And don't forget to use implicit none liberally to reduce the surprise potential.

Upvotes: 2

Related Questions