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.
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.
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.
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.
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.
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);
}
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])
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With