How to pass Ref of struct containing Cstring to Fortran

I have a struct that contains a Cstring. I saw this older post about how to pass such a struct to C:

I need to pass such a struct to Fortran (the Fortran code converts the C-style string to a Fortran string), so that means I need to pass a Ref of my struct. I’m struggling to define the appropriate Base.cconvert and Base.unsafe_convert methods.

My current attempt is essentially trying to put a Ref around the answer from the post linked above. Simplifying things a bit:

struct Foo
    s::String
end

struct FooC
    s::CString
end

# Base.cconvert(::Type{Cstring}, s::String) just returns s, so we can just pass through the struct, wrapping it with a Ref
Base.cconvert(::Type{Ref{FooC}}, foo::Foo) = Ref(foo)

function Base.unsafe_convert(::Type{Ref{FooC}}, foo)
    Base.unsafe_convert(Ptr{FooC}, Ref(FooC(Base.unsafe_convert(Cstring, foo[].s))))
end

foo = Foo("hi")
@ccall my_lib.my_fun(foo::Ref{FooC})::Cvoid

My code yields signal (10.1): Bus error: 10. I gather that means that the pointer I’m passing to Fortran is referencing memory that the current process isn’t allowed to access.

Any help would be greatly appreciated! Thanks!

I’m embarrassed to report that I had a mismatch in my struct definitions between Fortran and Julia, which was the cause of my signal (10.1).

After fixing that, I made a new attempt at defining the Base.cconvert and Base.unsafe_convert methods. I’m still confused about how to properly handle Ref. Unwrapping to apply Base.unsafe_convert and then re-wrapping with Ref doesn’t seem to work.

I decided to use the fallback implementation of Base.unsafe_convert for dealing with Ref. I was able to get it to work by doing the following:

struct Foo
    s::String
end

struct FooC
    s::CString
end

function Base.cconvert(::Type{FooC}, foo::Foo)
    FooC(Base.unsafe_convert(Cstring, Base.cconvert(Cstring, foo.s)))
end

Base.convert(T::Type{FooC}, foo::Foo) = Base.cconvert(T, foo)

foo = Foo("hi")
@ccall my_lib.my_fun(foo::Ref{FooC})::Cvoid

I’m not sure if it is OK that I called Base.unsafe_convert for the String field within the Base.cconvert call for the containing struct.

1 Like

I think you should probably overload Base.unsafe_convert rather than Base.cconvert or Base.convert then.

julia> struct Foo
           s::String
       end

julia> struct FooC
           s::Cstring
       end

julia> Base.cconvert(::Type{FooC}, foo::Foo) = foo

julia> Base.unsafe_convert(::Type{FooC}, foo::Foo) = FooC(Base.unsafe_convert(Cstring, foo.s))

julia> my_c_function(fooc::FooC) = println(Base.unsafe_string(fooc.s))
my_c_function (generic function with 1 method)

julia> f_ptr = @cfunction(my_c_function, Nothing, (FooC,))
Ptr{Nothing} @0x00000235cfc63fe0

julia> fooey = Foo("Hello World")
Foo("Hello World")

julia> @ccall $f_ptr(fooey::FooC)::Nothing
Hello World

Here’s some highlighting of the relevant documentation.

help?> @ccall
  ...

  Each argvalue to @ccall is converted to the corresponding argtype, by automatic insertion of calls to
  unsafe_convert(argtype, cconvert(argtype, argvalue)). (See also the documentation for unsafe_convert and cconvert
  for further details.) In most cases, this simply results in a call to convert(argtype, argvalue).

help?> Base.unsafe_convert
  unsafe_convert(T, x)

    In cases where convert would need to take a Julia object and turn it into a Ptr, this function should be used to
  define and perform that conversion.

  Be careful to ensure that a Julia reference to x exists as long as the result of this function will be used.
  
help?> Base.cconvert
  cconvert(T,x)

    In cases where x cannot be safely converted to T, unlike convert, cconvert may return an object of a type different
  from T, which however is suitable for unsafe_convert to handle. The result of this function should be kept valid
  (for the GC) until the result of unsafe_convert is not needed anymore. This can be used to allocate memory that will
  be accessed by the ccall. If multiple objects need to be allocated, a tuple of the objects can be used as return
  value.

  Neither convert nor cconvert should take a Julia object and turn it into a Ptr.

Your example isn’t passing a Ref{FooC}. What you have indeed makes sense for the non-Ref case. But once we have a Ref, it seems like I’d need to unwrap it in order to apply Base.unsafe_convert to the field.