Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 659
alexurba Avatar asked Sep 01 '14 16:09

alexurba


People also ask

How do I Pass strings between C and Fortran?

This is the simplest Fortran-2003-standard approach to string interoperation between C and Fortran. Fortran 2018 provides more convenient ways to pass strings (including allocatable strings) between Fortran and C, some of which require no change to the Fortran code and minimal work on the C code via ISO_Fortran_binding.h.

What is the difference between C and Fortran string arguments?

Fortran handles its string arguments differently from the C language. In Fortran, along with the string, length of the string is also passed as a hidden argument to the called function.

How to pass strvec_DESC in C to a Fortran function?

Here in the C code, we have established a Fortran descriptor StrVec_desc via CFI_establish from ISO_Fortran_binding.h header file that correctly passes all the information needed about the string StrVec in C to the Fortran function. This descriptor is then passed to the Fortran function instead of the string itself.

How to get lower case from a string in Fortran?

Consider the following example Fortran function getLowerCase which takes a string of arbitrary length as input and converts all of the upper-case letters to lower-case and returns the result as the output string, Rewrite this function in such a way that it can be called from the C programming language.


2 Answers

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);
}
like image 60
Vladimir F Героям слава Avatar answered Sep 27 '22 18:09

Vladimir F Героям слава


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

like image 36
alexurba Avatar answered Sep 27 '22 16:09

alexurba