alexurba
alexurba

Reputation: 1664

Passing an array of C-strings to Fortran (iso_c_binding)

How can I pass an array of C strings (char* cstrings[]) to a Fortran subroutine?

Question Arrays of strings in fortran-C bridges using iso_c_binding is definitely related, but the answer does not seem correct and does not even compile with GNU Fortran.

I am currently working on a C interface for a Fortran code, and I expected that iso_c_binding (which I had used previously) would make this a piece of cake. No luck so far for arrays of C strings...

The Fortran subroutine should take an array of strings as an argument. In plain Fortran I would write something like the following:

subroutine print_fstring_array(fstring)

  implicit none

  character(len=*), dimension(:), intent(in) :: fstring
  integer                                    :: i

  do i = 1, size(fstring)
    write(*,*) trim(fstring(i))
  end do

end subroutine print_fstring_array

One way to pass a single C string to Fortran is as C pointer (c_ptr) (I know, I could also use an array of character(kind=c_char))

subroutine print_cstring(cstring) bind(C)

  use iso_c_binding, only: c_ptr, c_f_pointer, c_loc, c_null_char
  implicit none

  type(c_ptr), target, intent(in) :: cstring
  character(len=1024), pointer    :: fstring
  integer                         :: slen

  call c_f_pointer(c_loc(cstring), fstring)
  slen = index(fstring, c_null_char) - 1
  write(*,*) fstring(1:slen)

end subroutine print_cstring

So, I assumed an array of c_ptr would be a good idea

subroutine print_cstring_array(n, cstring) bind(C)

  use iso_c_binding, only: c_ptr, c_int, c_f_pointer, c_loc, c_null_char
  implicit none

  integer(kind=c_int),               intent(in) :: n
  type(c_ptr), dimension(n), target, intent(in) :: cstring
  character(len=1024), pointer                  :: fstr
  integer                                       :: slen, i

  do i = 1, n
    call c_f_pointer(c_loc(cstring(i)), fstring)
    slen = index(fstring, c_null_char) - 1
    write(*,*) fstring(1:slen)
  end do

end subroutine print_cstring_array

but this produces a segmentation fault.

The C code for the last example is

# include "stdio.h"
# include "fstring.h"

void main(void) {
  char* cstring[] = { "abc", "def", "ghi", "jkl" };
  int n = 4;
  print_cstring_array(&n, cstring);
}

and the contents of the header file fstring.h are simply:

void print_cstring_array(int* n, char* cstring[]);

I am targeting GNU Fortran and Intel Fortran and have tested the above with GNU Fortran. The lengths of the strings is fixed (3 in the above example), in case this simplifies the solution. However, the dimension of the array can vary.

Any pointers (even C pointers) would be greatly appreciated.

Upvotes: 6

Views: 3437

Answers (2)

alexurba
alexurba

Reputation: 1664

Sometimes the solution is easier than expected. It turned out that c_f_pointer(cptr, fptr[, shape]) takes an array shape as optional argument to convert arrays of C pointers (I missed that in the reference):

subroutine print_cstring_array(n, cstring) bind(C)

  use iso_c_binding, only: c_ptr, c_int, c_f_pointer, c_loc, c_null_char
  implicit none

  integer(kind=c_int),                 intent(in) :: n
  type(c_ptr), target,                 intent(in) :: cstring
  character(kind=c_char), dimension(:,:), pointer :: fptr
  character(len=3), dimension(n)                  :: fstring

  call c_f_pointer(c_loc(cstring), fptr, [3, n])
  do i = 1, n
     slen = 0
     do while(fptr(slen+1,i) /= c_null_char)
        slen = slen + 1
     end do
     fstring(i) = transfer(fptr(1:slen,i), fstring(i))
     write(*,*) trim(fstring(i))
  end do                                                

end subroutine print_cstring_array

Thanks to @alk for pointing me to How to pass arrays of strings from both C and Fortran to Fortran?, because there I realized the optional shape argument of c_f_pointer(cptr, fptr[, shape]).

Upvotes: 1

The largest problem in your code in the question is you use c_f_pointer(c_loc(cstring), instead of c_f_pointer(cstring,.

This works for me:

subroutine print_cstring_array(n, cstring) bind(C)

  use iso_c_binding, only: c_ptr, c_int, c_f_pointer, c_loc, c_null_char
  implicit none

  integer(kind=c_int),               intent(in) :: n
  type(c_ptr), dimension(n), target, intent(in) :: cstring
  character, pointer                            :: fstring(:)
  integer                                       :: slen, i

  do i = 1, n
    call c_f_pointer(cstring(i), fstring, [4])
    write(*,*) fstring
  end do

end subroutine print_cstring_array



# include "stdio.h"

void print_cstring_array(int* n, char* cstring[]);

void main(void) {
  char* cstring[] = { "abc", "def", "ghi", "jkl" };
  int n = 4;
  print_cstring_array(&n, cstring);
}

Upvotes: 9

Related Questions