Home > Back-end >  Pass dynamically allocated array of strings from C to Fortran
Pass dynamically allocated array of strings from C to Fortran

Time:06-25

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 enough len= 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

enter image description here

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
  • Related