6

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.

Community
  • 1
  • 1
alexurba
  • 1,674
  • 18
  • 13
  • 1
    Please note: `char* cstrings[]` is an array of **pointer** to "strings", it does not necessarily refer to a continous block of memory. Have a look here: http://stackoverflow.com/q/13869528/694576 and note the difference in how the "string" array is defined. – alk Sep 01 '14 at 17:32
  • Thanks @alk. That might be the issue here. My C skills are not very good. How would I make it continuous? – alexurba Sep 01 '14 at 17:34
  • "*How would I make it continuous?*" By doing it as the code in the question I linked does. – alk Sep 01 '14 at 17:36
  • The google key for such issues is "*Mixed Language Programming*". – alk Sep 01 '14 at 17:45
  • Thanks @alk, that link was not in your initial comment. Indeed, that answer looks promising. Now I am trying to grasp the details. – alexurba Sep 01 '14 at 17:48
  • Also @alk, believe me that I have spent hours Google'ing for a solution. There are many forum posts that seemed related at first glance, but turned out not to be useful. This is in fact the first time that I resort to posting a question on SO. – alexurba Sep 01 '14 at 17:51
  • Good luck. It simply is to long ago I did exactly this. The backup of the code is on backup of a backup ... sorry. – alk Sep 01 '14 at 17:55
  • Which part you want to keep (more or less), your C code here, or your Fortran subroutine? – Vladimir F Героям слава Sep 01 '14 at 18:06
  • @alk - I solved it. I simply was not aware that `c_f_pointer(cptr, fptr[, shape])` takes an array shape as optional third argument (found in the answer you pointed to). You are welcome to post this as an answer. Otherwise I will do myself, as I think it is useful for other people and not identical to the linked question. – alexurba Sep 01 '14 at 18:08
  • @Vladimir - I am writing the Fortran part, but the C part should be as straightforward as possible for users. Anyway, thanks to `alk` I have solved the issue. – alexurba Sep 01 '14 at 18:11

2 Answers2

9

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);
}
  • Thanks for answering. However, I think it is pure luck (or compiler dependent) that the call to `c_f_pointer` works in your example. You definitely need the C memory location from `c_loc`. See for example [`c_f_pointer` (Intel Fortran)](https://software.intel.com/sites/products/documentation/doclib/stdxe/2013/composerxe/compiler/fortran-mac/GUID-7D76F195-E490-4765-B55E-32DF74168AE7.htm). – alexurba Sep 01 '14 at 18:47
  • 2
    @alexurba No, it is OK. I pass `type(C_PTR)` to `c_f_pointer` which is of correct type. Your reference confirms that, although I know `c_f_pointer` quite well. – Vladimir F Героям слава Sep 01 '14 at 18:59
  • @VladimirF - I try to pass a 1-d `char *` : `char *cstring = {"hello world"};`, (skipping the `dimension(n)` in declaration of cstring as well as the do-loop) and just using `call c_f_pointer(cstring, fstring, [lengthofcstring])` but all I get in fstring are lengthofcstring (=11) undefined pointers. Why is your last parameter in c_f_pointer = 4? Shouldn't it be 3? – Erik Thysell Feb 03 '16 at 07:23
  • Yes, 3, but 4 works too, the fourth null character is appended by C and is not printed. – Vladimir F Героям слава Feb 03 '16 at 07:56
1

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]).

Community
  • 1
  • 1
alexurba
  • 1,674
  • 18
  • 13