I think there’s a pattern involving cconvert
and unsafe_convert
here, with two structs. One for using on the Julia side, one for the actual FFI interop, which would only exist for the duration of the FFI call. I’m not 100% sure on the exact definitions necessary, so the following may not necessarily be correct:
// The C example just returns the first element of the array
typedef struct {
int* data;
} CExample;
int printarr(CExample* ex) {
return ex->data[0];
}
mutable struct JLExample
data::Vector{Int64}
end
mutable struct CExample
data::Ptr{Int64}
end
Base.cconvert(::Type{Ptr{CExample}}, jle::JLExample) = CExample(pointer(jle.data))
function Base.unsafe_convert(::Type{Ptr{CExample}}, ce::CExample)
return convert(Ptr{CExample}, pointer_from_objref(ce))
end
The reason I’m not 100% sure on this is because of that pointer(jle.data)
in cconvert
. This is likely illegal - cconvert
isn’t supposed to take pointers, that’s the job of unsafe_convert
here. The manual seems self contradictory here, since it says this:
Base.cconvert
normally just calls convert
, but can be defined to return an arbitrary new object more appropriate for passing to C. This should be used to perform all allocations of memory that will be accessed by the C code. For example, this is used to convert an Array
of objects (e.g. strings) to an array of pointers.
but also this:
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
.
which seems to imply that the conversion of an Array
of objects to an array of pointers would be illegal…
Either way, it would be used like so:
julia> function bar()
ex = JLExample([15,2,3])
GC.@preserve ex begin
@ccall "./example.so".printarr(ex::Ref{CExample})::Cint
end
end
bar (generic function with 1 method)
julia> bar()
15
which works, but certainly feels a little ugly due to the need for that GC.@preserve
. The GC.@preserve
should make the definitions above safe, since ex
(and by extension, the wrapped array) is not allowed to be freed until the end of that begin
block. This shouldn’t be necessary though, and I’m pretty sure I’m missing the correct way of doing this Would be really great to get such an example involving a more complicated struct into the docs…