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.
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.
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.