3

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.

M.E.
  • 4,955
  • 4
  • 49
  • 128
  • 1
    On a style note, having seen a few of your recent questions: it's arguably bad practice to assume that default intrinsic type parameters are the interoperable ones. Here you assume the default kind character is the same as the interoperable one. You may be better explicitly working with interoperable types throughout, or explicitly converting them. – francescalus Jul 21 '21 at 01:54

2 Answers2

2

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
  • 30,576
  • 16
  • 61
  • 96
  • Super clear explanation on both issues, not only fixes them but it clearly clarifies why I was getting both the warning and the error. Once I fixed the assumed size vs assumed shape issue, using `//` was making C print `F` instead of `Foo`, which is consistent with the explanation. Also I did not know which was the difference between `(*)` and `(:)` which is clear now. – M.E. Jul 21 '21 at 06:59
  • 1
    I've added a link to help others understand the difference between the two array types. – francescalus Jul 21 '21 at 11:38
0

@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
---
Scientist
  • 1,767
  • 2
  • 12
  • 20
  • 1
    Regarding the concatenation, you have also changed the dummy argument `text` to be an assumed-length scalar rather than an assumed-size or assumed-shape array. This is a significant change, and means `text // C_NULL_CHAR` is concatenation of two scalars rather than an array and a scalar. – francescalus Jul 21 '21 at 01:26
  • But still, I am kind of puzzled by whether and how the assumed-length character is interoperable with C. Is it interoperable? Or is it just pure luck to see this code working? Normally, I would assume a conversion to a character vector be necessary before passing to C. – Scientist Jul 21 '21 at 01:53
  • It's not a question of "interoperable", as such. The scalar character actual argument `text` can be argument associated with the array character dummy argument `t` using sequence association. (That's true whether the procedure is defined by Fortran or otherwise.) – francescalus Jul 21 '21 at 01:58
  • The example provided does not work in my environment (f2py, gfortran, FreeBSD), anyway I have followed both advices from francescalus and they work, plus I understand the explanation. `[text, C_NULL_CHAR]` instead of `text // C_NULL_CHAR` is also needed. – M.E. Jul 21 '21 at 07:04
  • This is a likely bug with your gfortran. Which version are you using? GFortran 10 happily compiles and runs without problems. – Scientist Jul 21 '21 at 15:47