How do I concatenate a NULL character to a character array in Fortran to call a c function?

Viewed 189

I have the following test function:

#include <stdlib.h>
#include <stdio.h>

void print_string(char *text);

void print_string(char *text) {
    printf("---\n%s\n---\n", text);
}

And the following module which encapsulates the call with a Fortran subroutine using iso c bindings:

module test_c_lib

        use iso_c_binding
        implicit none
                
contains
        
    subroutine test(text)
    
            use iso_c_binding
            
            character,intent(in)       :: text(:)
            
            ! Interface to C function
            interface
                    subroutine c_print_string(t) bind(C, name="print_string")
                            import
                            character(kind=c_char) :: t(:)
                    end subroutine
            end interface
            
            ! Call C function
            print *,"AAAAA"
            print *,text
            print *,"AAAAA"         
            
            call c_print_string(text // C_NULL_CHAR)
            
    end subroutine
    
end module

The way to define the subroutine in Fortran using ISO C BINDINGS is extracted from this documentation link. I further encapsulate it a bit as f2py does not support iso c bindings.

I compile everything via makefile:

$ cat makefile 
f_mod.so:       f_mod.f90 c_lib.o
                f2py -c f_mod.f90 c_lib.o -m f_mod

c_lib.o:        c_lib.c
                gcc -c -fpic c_lib.c -o c_lib.o

It compiles but:

  1. I get the following warning:
      150 |                 call c_print_string(text // C_NULL_CHAR)
          |                                    1
    Warning: Character length mismatch (2/1) between actual argument and assumed-shape dummy argument 't' at (1) [-Wargument-mismatch]
  1. I get the following output when invoking via import fmod; f_mod.test_c_lib.test("Foo"):
     AAAAA
     Foo
     AAAAA
    ---
    8v$
    ---

So f2py is working but when passing text // C_NULL_CHAR as parameter it does not seem to work as I get garbage from the C function output.

2 Answers

You have two mistakes:

To make the character dummy argument interoperable with the char * C parameter, t should be an assumed size array:

character(kind=c_char) :: t(*)   ! Assumed size, not assumed shape

You can also use the CFI_cdesc_t C type to have t an assumed shape array, or an assumed length scalar, but that's a lot more advanced.

Even making t assumed size, you don't have a working procedure, because of the next problem: the elemental nature of //.

As text is an (assumed shape) array the concatenation text // C_NULL_CHAR is done elementally, giving the length-2 array1 with each element of text concatenated with the C null char. The C function then sees input looking like [text(1), C_NULL_CHAR, text(2), C_NULL_CHAR, ...].

To have a length-1 character array with C_NULL_CHAR appended, you need to use an array constructor:

call c_print_string([text,C_NULL_CHAR])

1 That the argument is of length-2 is the reason for the warning about a "character length mismatch".

@francescalus's answer is correct. But since I solved it before seeing his answer, I post the fully functional code here:

module test_c_lib

        use iso_c_binding
        implicit none

contains

    subroutine test(text)

        use iso_c_binding

        character(*),intent(in) :: text

        ! Interface to C function
        interface
                subroutine c_print_string(t) bind(C, name="print_string")
                        import
                        character(kind=c_char) :: t(*)
                end subroutine
        end interface

        ! Call C function
        print *,"Inside Fortran"
        print *,text
        print *,"Inside Fortran"

        call c_print_string(text // C_NULL_CHAR)

    end subroutine

end module test_c_lib

program test_prog
    use test_c_lib
    call test(text = "Hello C World")
end program test_prog

Note the changes made to the interfaces of subroutine test(text) and the C function interface print_string(). Compile the codes via the following commands,

icl print_string.c -c
ifort test_c_lib.f90 -c
ifort *.obj /exe:main.exe

I am not sure at the moment if this is a bug in Intel compiler or it is the Standard conforming behavior to not need the last correction by @francecalus to convert text // C_NULL_CHAR to [text,C_NULL_CHAR]. Certainly ifort does not need it. Here is the output:

> main.exe
 Inside Fortran
 Hello C World
 Inside Fortran
---
Hello C World
---
Related