How to keep a reference for C structure to avoid GC?

@yuyichao, I’d take the opportunity here to ask a related question:

struct A
   x::Vector{Int}
end
struct B
   ptrA::Ptr{A}
end
a = A([1,2,3])
b = B(pointer_from_objref(Ref(a))

I need to pass b (or its Ptr{B}) to ccall. If I understand correctly I should wrap functions getting b with GC.@preserve:

GC.@preserve a begin
   some_function_ccalling_mayyybeee(b)
end

You are getting a garbage pointer here. If you want ptrA to point to A then you need to copy what ccall(... (Ref{A},), a::A) does. i.e. v = cconvert(Ref{A}, a); GC.@preserve v begin b = B(unsafe_convert(Ref{A}, v)) end

yes, that was a contrived example (which was obviously wrong), thanks for clarification!

After a while of investigation two more questions:

  • Ref(a::A) → Base.RefValue(a::A) → Base.RefValue{A}(a), whereas Base.cconvert(Ref{A}, a) → convert(Ref{A}, a) → Base.RefValue{A}(a). Why do you suggest ra = Base.cconvert(Ref{A}, a) instead of simple ra = Ref(a)?

  • On the other hand Base.unsafe_convert(Ref{A}, ra) → Base.unsafe_convert(Ptr{A}, v) → black_magic:wink: so Base.unsafe_convert(Ref{A}, ra) leads to the correct thing, but why is this encouraged instead of more direct Base.unsafe_convert(Ptr{A},ra)?

Because there are two way to do this,

  1. You know how to do the black magic part and then you can just write ra = Ref(a) and do the black magic part yourself. That’s totally fine.
  2. You don’t know how to do the black magic part and then you should use the generic mechanism setup for this, i.e. matching pair of cconvert and unsafe_convert.

Either way is fine, However, you must use unsafe_convert and cconvert in matching pair, the type and value for the intermediate result (i.e. return value of cconvert) is completely implementation detail and isn’t something you should rely on (unless you implemented it yourself, of course).


Therefore,

and

Are both looking at implementation detail.


This is fine as long as you don’t call any unsafe_convert on it.


And this would be wrong if you use cconvert on Ref{A}. If you call Base.unsafe_convert(Ptr{A},ra), ra must be the return value from a Base.cconvert(Ptr{A}, ...) (see below)


Now what I said above is not the only way to use matching cconvert and unsafe_convert. Converting to Ptr{A} is also allowed but you just won’t get it from a normal object off type A. You need to replace it with Ref(a). In another word, it is valid and well supported too if you replace ra = cconvert(Ref{A}, a) with ra = cconvert(Ptr{A}, Ref(a)) and unsafe_convert(Ref{A}, ra) with unsafe_convert(Ptr{A}, ra).

2 Likes

That was very enlightening, thanks a lot!