I am trying to pass a dynamically allocated array of strings (char ***str
) from C to Fortran but I seem to be missing something when it comes to dereferencing the C pointer in Fortran thus yielding garbage strings as output (see MWE below).
Side Question
Is calling deallocate(fptr)
enough to stop the program below from leaking memory (I would think not)? If not would it be possible to free msgs
(in C) from Fortran?
To Reproduce
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void str_alloc(char*** msgs, size_t * n) {
*n = 3;
int str_len = 10;
(*msgs) = malloc(*n * sizeof(char*));
for (int i = 0; i < *n; i ) {
(*msgs)[i] = malloc((str_len 1) * sizeof(char));
strcpy((*msgs)[i], "012346789");
}
}
program main
use iso_c_binding
implicit none
interface
subroutine str_alloc(msgs, n) bind(C, name="str_alloc")
use iso_c_binding
type(c_ptr), intent(out) :: msgs
integer(c_size_t), intent(out) :: n
end subroutine
end interface
integer, parameter :: STRLEN = 10
character(kind=c_char, len=STRLEN), allocatable :: names(:)
call str_array_from_c(names)
contains
subroutine str_array_from_c(strs)
character(len=STRLEN), allocatable, intent(out) :: strs(:)
type(c_ptr) :: cptr
integer(c_size_t) :: i, n, lenstr
character(kind=c_char, len=1), pointer :: fptr(:,:)
call str_alloc(cptr, n)
call c_f_pointer(cptr, fptr, [int(STRLEN, kind=c_size_t), n])
allocate(strs(n))
print*, "Output C-str array from fortran"
do i = 1_c_size_t, n
lenstr = cstrlen(fptr(:, i))
strs(i) = transfer(fptr(1:lenstr,i), strs(i))
print*, fptr(1:STRLEN, i), "|", strs(i)
end do
deallocate(fptr)
end subroutine str_array_from_c
!> Calculates the length of a C string.
function cstrlen(carray) result(res)
character(kind=c_char, len=1), intent(in) :: carray(:)
integer :: res
integer :: i
do i = 1, size(carray)
if (carray(i) == c_null_char) then
res = i - 1
return
end if
end do
res = i
end function cstrlen
end program main
Compile
gcc -c -g -Wall -o cfile.o cfile.c
gfortran -g -Wall -o main.o cfile.o main.f90
Output
Output C-str array from fortran
0�P�|0�
p�|
!|
Additional Info
- I cannot edit the C interface.
- The C
str_len
in reality is variable and hence the array of strings is jagged. As far as I know, the only solution, without defining a custom type, is using a large enoughlen=
of characters. - I am relatively convinced that my mistake is how I dereference the pointer since inspecting the hex dump of
cptr
shows the strings are present
Related posts
How to pass arrays of strings from both C and Fortran to Fortran?
Passing an array of C-strings to Fortran (iso_c_binding)
CodePudding user response:
On the C side you have an array of pointers, which is NOT the same as a two-dimensional Fortran array. Fortran doesn't have the "array of pointers" concept, so you have to have an array of derived type with a pointer component.
First, the cptr returned from the C code has to be converted to an array of the derived type, each element of which contains a pointer to the string. You then iterate through each of these, converting the subcomponent C_PTR to a Fortran pointer, which is then your array of characters.
Second, you can't DEALLOCATE
something that Fortran didn't allocate. You would have to call back into C to free those strings.
Here is a revised version of your Fortran code that works. The C code didn't change.
program main
use iso_c_binding
implicit none
interface
subroutine str_alloc(msgs, n) bind(C, name="str_alloc")
use iso_c_binding
type(c_ptr), intent(out) :: msgs
integer(c_size_t), intent(out) :: n
end subroutine
end interface
integer, parameter :: STRLEN = 10
character(kind=c_char, len=STRLEN), allocatable :: names(:)
call str_array_from_c(names)
contains
subroutine str_array_from_c(strs)
character(len=STRLEN), allocatable, intent(out) :: strs(:)
type(c_ptr) :: cptr
type c_array_t
type(c_ptr) :: s
end type c_array_t
type(c_array_t), pointer :: c_array(:)
integer(c_size_t) :: i, n, lenstr
character(kind=c_char, len=1), pointer :: fptr(:)
call str_alloc(cptr, n)
call c_f_pointer(cptr,c_array, [n])
allocate(strs(n))
print*, "Output C-str array from fortran"
do i = 1_c_size_t, n
call c_f_pointer(c_array(i)%s, fptr, [int(STRLEN, kind=c_size_t)])
lenstr = cstrlen(fptr)
strs(i) = transfer(fptr(1:lenstr), strs(i))
print*, fptr(1:STRLEN), "|", strs(i)
end do
end subroutine str_array_from_c
!> Calculates the length of a C string.
function cstrlen(carray) result(res)
character(kind=c_char, len=1), intent(in) :: carray(:)
integer :: res
integer :: i
do i = 1, size(carray)
if (carray(i) == c_null_char) then
res = i - 1
return
end if
end do
res = i
end function cstrlen
end program main
When I build and run this I get:
Output C-str array from fortran
012346789|012346789
012346789|012346789
012346789|012346789