Julia passing a string to Fortran

How can I pass a string to Fortran? The following methods doesn’t seem to work

julia> T = Ref{Char}
Ref{Char}

julia> Base.unsafe_convert(T, Base.cconvert(T, "foo"))
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Char

Here, If I choose T to be Ref{Char}, Ref{String}, Ref{Cstring}, unsafe_convert errors. If instead I choose T to be Cstring or String, the Fortran side seems to get an empty string.

Minimum example:

module julia_interface

implicit none

public :: print_name

private

CONTAINS

subroutine print_name(name)
    character(len=*), intent(in) :: name
    print *, name
end subroutine print_name

end module julia_interface
ccall(
    (:julia_interface_mp_print_name_, "./julia_interface.so"),
    Cvoid,
    (Cstring,),
    "foo"
)

Fortran strings are fix-length so Vector{Char} instead of String / Cstring? (which are null terminated)

Here’s what I get:

ERROR: LoadError: MethodError: Cannot `convert` an object of type String to an object of type Vector{Char}

with recent gfortran,

ccall((:__julia_interface_MOD_print_name, "./libx.so"), Cvoid, (Ptr{UInt8}, Clong), str, length(str))

works.
Otherwise, you may need to read the fine print in your compiler manual.

2 Likes

Care to share Mod_print_name?

The best thing is to define ISO_C_BINDING interfaces on the Fortran side. Otherwise the string-passing ABI is compiler-dependent AFAIK.

3 Likes

That’s just the linkage symbol I got by compiling your Fortran code with gfortran.

No. Char is 4 bytes but uses a Julia-specific encoding (that maps directly to UTF8). Fortran characters are 1-byte by default in most compilers, and were intended originally for ASCII but maybe could be used for UTF-8 encoded Unicode if the Fortran code doesn’t look at them too closely. Some Fortran compilers also support UTF32 or UTF16 (sometimes archaically called ucs4 and ucs2) via different character “kinds”, but only if the Fortran code declares it explicitly.

But never pass Char data directly. If the Fortran code is expecting UTF32, you can get it via [UInt32(c) for c in str]

4 Likes

Here is how it works for me:

Fortran interface

module julia_interface

use ISO_C_BINDING

implicit none

public :: print_name

private

CONTAINS

subroutine print_name(name, length)
    character(kind=C_CHAR, len=length), intent(in) :: name
    integer, intent(in) :: length
    print *, name
end subroutine print_name

end module julia_interface

Julia ccall

str = "foo"
ccall(
    (:julia_interface_mp_print_name_, "./julia_interface.so"),
    Cvoid,
    (Cstring, Ref{Int32}),
    str, length(str)
)

Honestly, I still don’t understand exactly what is happening under the hook. I learned a bit about Fortran during the past two days, with the sole purpose of wrapping a subroutine in Julia. I assume Julia will automatically add a \0 to foo so the actual length is 4, which is not needed on the Fortran side?

Actually, Julia strings internally already have a zero byte at the end of their data, to make it easier to pass them to C libraries without copying the data.

When you declare the argument as Cstring, however, it checks that the string contains no 0 bytes (and throws an error if there are), since this would lead to a corrupted string in a C routine expecting NUL-terminated data. You don’t want that here because Fortran doesn’t treat 0 as the end of the string, and you are passing the length explicitly. So you should declare the argument as Ptr{UInt8} instead.

1 Like

You should declare this as subroutine print_name(name, length) bind(C). That way you should be able to ccall it as :print_name without compiler-specific name-mangling (:julia_interface_mp_print_name_ is not portable). See e.g. the gfortran docs on interoperating with C.

I would declare this as integer(C_INT) rather than integer, in which case you can use Cint (rather than Int32) as the argument type in Julia and be sure that will work, even if the Fortran code is compiled in ILP64 mode.

Better yet, declare it as integer(C_INTPTR_T), in which case you can simply pass the size as Int (the corresponding type in Julia) and it will work on a 64-bit machine even in the unlikely event that your string is more than 2GiB in length.

You can also use the VALUE attribute for the variable, in which case you don’t need Ref in the ccall (the value is passed directly, not as a pointer).

3 Likes

Thanks, here is my new interface

module julia_interface

use ISO_C_BINDING

implicit none

public :: print_name

private

CONTAINS

subroutine print_name(name, length) bind(C)
    character(kind=C_CHAR), dimension(*), intent(in) :: name
    integer(kind=C_Int), value, intent(in) :: length
    print *, name(1:length)
end subroutine print_name

end module julia_interface

and ccall

str = "foo"
ccall(
    (:print_name, "./julia_interface.so"),
    Cvoid,
    (Ptr{UInt8}, Cint),
    str, length(str)
)

Just want to mention I have to change name to assumed-size array because otherwise the compiler complains “A character dummy argument with length other than 1 is not interoperable”.