Home > database >  Fortran and C interoperability: passing char *string as a function's argument to Fortran
Fortran and C interoperability: passing char *string as a function's argument to Fortran

Time:01-30

I would like to create a fortran binding to a C function that returns an arbitrary length string as a function argument. I have read this similar question, but I simply cannot get the solution there to work. I have simplified the problem as much as I can so I can explain here.

I have a C function like this: c_string.c (I know that usually this kind of function would also have an argument that also returns the length of the string, but I have left it out here for simplicity, since in this example I know the length.)

#include <string.h>

void make_string(char *string) {
    strcpy(string, "sand");
}

It seems to work fine in a C program, for example:

#include <stdlib.h>
#include <stdio.h>
#include "c_string.c"

int main() {
    char *string;
    string = malloc(5*sizeof(char));
    make_string(string);
    printf(string);
}

I want to call this function in a Fortran program: main.f90. So I make a c binding following the solution mentioned above.

program main
    use iso_c_binding
    implicit none
    interface
        subroutine c_make_string(string) bind (C, name='make_string')
            import
            type (c_ptr), value :: string
        end subroutine c_make_string
    end interface

    type (c_ptr) :: c_string
    character, pointer :: local_string(:)

    call c_make_string(c_string)
    call c_f_pointer(c_string, local_string, [4])
    write (*,*) local_string
end program main

But this program results is a segfault at the write statement. Trying to access the resulting local_string pointer by indexing gives a segfault too. Note: after adding the value attribute to the interface, the segfault is now in the C function.

The only solution I came up with is to not use c_ptr and c_f_pointer(), and just use a maximum buffer length like 512.

program main
    use iso_c_binding
    implicit none

    interface
        subroutine c_make_string(string) bind (C, name='make_string')
            import
            character (len=1, kind=c_char) :: string(512)
        end subroutine
    end interface

    character (len=1, kind=c_char) :: local_string(512)

    call c_make_string(local_string)
    write (*,*) local_string
end program main

But that results in a whole bunch of junk after the useful data in the string. Of course, that is easily worked around if you know the length of the intended string, but it feels unsatisfactory to me... if only I knew what was going wrong with the c_f_pointer() call in the original solution. Should I allocate space for the local_string before, as I do in the C program? But how? Why does it seem I need fixed length arrays for the binding? What is the clean solution to passing char * variables of arbitrary length via subroutine/function arguments?

CodePudding user response:

You do not allocate the memory for the string anywhere. You are just passing an undefined pointer to the C function and then the C function tries to copy some character data to this undefined address.

Either pre-allocate the string in Fortran, pass its length to C and use strncpy or malloc the string in C but then you will have to free it from C.


For example:

#include <string.h>

void copy_string(char *string, int n) {
    strncpy(string, "sand", n);
}
program main
    use iso_c_binding
    implicit none
    interface
        subroutine c_copy_string(string, n) bind (C, name='copy_string')
            import
            type (c_ptr), value :: string
            integer(c_int), value :: n
        end subroutine
    end interface

    type (c_ptr) :: c_string
    character(c_char), pointer :: local_string(:)

    allocate(local_string(10))
    c_string = c_loc(local_string)
    call c_copy_string(c_string, int(size(local_string), c_int))
    
    write (*,*) local_string
    deallocate(local_string)
end program main
> gfortran -fsanitize=address,undefined make_string.c main.f90
> ./a.out 
 sand

If you really want to "make" the string in C, you have to make malloc the space for it. In that case, you need to pass the address by reference. Then then you either also have to pass the length or make a c_strlen function that finds out how large a null-delimited C string is (like here or here or here). There are examples of that already existing on the internet so it is not very useful to write all that from scratch here. The c_interface_module from fortranwiki could be very helpful here and if nothing, it shows good examples how to pass the strings back and forth between C and Fortran.

Remember, the string then has to be deleted from C, if it was malloced there.

  •  Tags:  
  • Related