I am trying to pass an array of strings from C to a Fortran subroutine as well as from Fortran to that same Fortran subroutine. I have managed to pass single strings (i.e. 1D character arrays) successfully from both C and Fortran. However, I'm having trouble with arrays of strings. I am using ISO C binding on the Fortran side, and ideally I'd like this to be as seamless as possible on the calling side.
I have read some related questions and answers. Some, (i.e. this and this) are simply "Use ISO C" without further details, which doesn't help much. This answer was very helpful (similar answer to a different question), but only works for single strings, where it seems that the c_null_char is recognized in the single Fortran string. I can't figure out what to do for the array case without having two separate routines.
What I currently have is a C routine which I want to pass the array of strings (string
) from:
#include <iostream>
extern "C" void print_hi_array(char input_string[][255]);
using namespace std;
int main() {
char string[3][255] = {"asdf","ghji","zxcv"};
print_hi_array(string);
return 0;
}
And, a similar Fortran routine:
program main
implicit none
call print_hi_array( (/"asdf", "ghji", "zxcv"/) )
end program
Thus far, this is what I have for the receiving end:
subroutine print_hi_array(input_string) bind(C)
use iso_c_binding, only: C_CHAR, c_null_char
implicit none
character (kind=c_char, len=1), dimension (3,255), intent (in) :: input_string
character (len=255), dimension (3) :: regular_string
character (len=255) :: dummy_string
integer :: i,j,k
write (*,*) input_string
do j = 1 , 3
dummy_string(:) = c_null_char
k = 1
do i = 1 + (j-1)*255, j*255,1
if (input_string(i) .ne. c_null_char) then
write (*,*) "i ",i,j, input_string(i)
dummy_string(k:k) = input_string(i)
endif
k = k +1
enddo
regular_string(j) = dummy_string
enddo
write (*,*) regular_string
end subroutine print_hi_array
This works for the C function; I get this output:
asdfghjizxcv
j= 1
i 1 1 a
i 2 1 s
i 3 1 d
i 4 1 f
j= 2
i 256 2 g
i 257 2 h
i 258 2 j
i 259 2 i
j= 3
i 511 3 z
i 512 3 x
i 513 3 c
i 514 3 v
asdf ghji zxcv
However, when it's done through Fortran I get nonsense out:
asdfghjizxcv@O,B�@(P,B�]B]6(P,B�@ .......
It seems there is no c_null_char
in this approach.
So, how can I write a Fortran subroutine to take in arrays of strings from both C and Fortran?
Fortran uses spaces to fill the rest of the string if it is declared longer than its stored text. It is not zero delimited, the declared length is stored in a hidden variable. It does not contain c null char and therefore you are reading some garbage (buffer overflow). What Fortran should print when tlit prints a string with \000 is undefined by the standard and depends on the implementation.
In particular, you are also passing a character(4) array with dimension 3 to a subroutine that expects much more data (255 chars, though I am not shure about the index order). Only pointers are passed so I think it can not be checked.
It is possible to define the length of the strings in the array constructor this way:
[character(255) :: "a","ab","abc"]
I see actually two ways to do that. Either, you write a loop in C and pass the strings one by one to Fortran, as you already did it before. Alternatively, if you want to pass the entire array and you want to handle the Fortran and the C arrays with the same routine, you will have to make an appropriate copy of your C-string array. Below a working, but not too much tested example:
extern "C" void print_array_c(int nstring, char input_string[][255]);
using namespace std;
int main() {
char string[3][255] = {"asdf","ghji","zxcv"};
print_array_c(3, string);
return 0;
}
Please note that I also pass the number of the strings, so that the example can handle arrays with various sizes. (The length of the strings is, however, assumed to be 255 characters.) On the Fortran size, one would need a routine to convert it Fortran strings. One possible visualization could be:
module arrayprint_module
use, intrinsic :: iso_c_binding
implicit none
integer, parameter :: STRLEN = 255
contains
!> The printing routine, works with Fortran character arrays only.
subroutine print_array(strings)
character(len=STRLEN), intent(in) :: strings(:)
integer :: ii
do ii = 1, size(strings)
write(*,*) ii, strings(ii)
end do
end subroutine print_array
!> Converts C string array to Fortran string array and invokes print_array.
subroutine print_array_c(nstring, cptr) bind(C)
integer(c_int), value :: nstring
type(c_ptr), intent(in), value :: cptr
character(kind=c_char), pointer :: fptr(:,:)
character(STRLEN), allocatable :: fstrings(:)
integer :: ii, lenstr
call c_f_pointer(cptr, fptr, [ STRLEN, nstring ])
allocate(fstrings(nstring))
do ii = 1, nstring
lenstr = cstrlen(fptr(:,ii))
fstrings(ii) = transfer(fptr(1:lenstr,ii), fstrings(ii))
end do
call print_array(fstrings)
end subroutine print_array_c
!> Calculates the length of a C string.
function cstrlen(carray) result(res)
character(kind=c_char), intent(in) :: carray(:)
integer :: res
integer :: ii
do ii = 1, size(carray)
if (carray(ii) == c_null_char) then
res = ii - 1
return
end if
end do
res = ii
end function cstrlen
end module arrayprint_module
Please note, that the array you pass from C must be contigous for this to work and I assumed that the character(kind=c_char) is compatible with the fortran character type, which usually it should be.
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