Creating C structs with pointers to other structs

Suppose we have the following structs matching some structs in a C header:

struct Foo
    a::UInt32
    b::Cstring
end

struct Bar
    a::UInt32
    b::Ptr{Foo}
end

And we have a function that takes a ptr to Bar. ccall will handle one conversion (from Bar to a Bar pointer) for us, and writing a function that takes a Bar and calls ccall will ensure a julia ref exists for the scope of the C function call.

However that’s not true for Bar’s field b. What’s the idiomatic way to create a pointer to a Foo, since pointer_from_objref should not be called on immutables?

As a follow on, what’s the “julian” way to redefine these types so that they use julia strings and refs and conversion “just works”?

I’m confused. Does ccall really do that conversion if you doesn’t overload Base.cconvert? What does your ccall signature look like? To pass Bar to a C function that accepts Ptr{Bar}, the common way is to wrap Bar into a Ref (Ref{Bar}) and then pass it to ccall and the conversion from Ref{Bar} to Ptr{Bar} just works. As for Foo, it’s similar.

fooref = Ref(foo_object)
GC.@preserve fooref  begin
    ptr = Base.unsafe_convert(Ptr{Foo}, fooref)
    ....some code using ptr....
end

Here, I used GC.@preserve to make sure that the fooref object lives for as long as I need ptr. Other strategies can also be employed (e.g. stashing fooref in some persistent data structure) if you need ptr to be valid for a longer time.

is GC.@protect the new syntax for GC.@preserve in the upcoming release?

You are correct, this was a total misconception on my behalf here.

Right, so first we need to create a julia reference, then use unsafe_convert for the pointer conversion, and make sure that reference sticks around so the GC doesn’t clean up the memory while we’re using it.

What is pointer_from_objref even for?

Sorry, that was a mistake — corrected now.

For rare applications that need to store “raw” pointers to the underlying native Julia object. For example, it is used in PyCall to stuff a reference to a native Julia object into a Python “wrapper” object that can be used to refer to the Julia object from Python.

1 Like

Would an example use of this be a C api that supports callbacks with user data (for example using a glfwGetWindowUserPointer in a window resize callback)? i.e. cases where we want a callback to be run on a native julia object?

(with the understanding that we’d probably have fewer headaches if we just captured that reference within the callback itself for most use cases)

No. These days, the pointer conversion in such cases can be done via Any arguments.

For example, suppose I have a C function that calls a user function with some pass-through data:

int dosomething(int (*callback)(int, void*), void *data)
{
     int i, s = 0;
     for (i = 0; i < 5; ++i)
          s += callback(i, data);
     return s;
}

and I want to be able to call it with a Julia function. I would write something like:

callback_wrapper(i, callback)::Cint = callback(i)
function dosomething(callback)
    c_callback_wrapper = @cfunction(callback_wrapper, Cint, (Cint, Any,))
    ccall((:dosomething,"libfoo"), Cint, (Ptr{Cvoid}, Any), c_callback_wrapper, callback)
end

and if you wanted to pass through an additional Julia object to your function you would then just use a closure, e.g.

julia> x = 3
3

julia> dosomething(i -> i * x) # pass additional object x via closure
30

julia> dosomething(i -> begin; println("callback($i)"); i * x; end)
callback(0)
callback(1)
callback(2)
callback(3)
callback(4)
30

This approach makes a lot more sense, since it’s not too far how one would handle such a callback object from C++.

Thanks so much for the help Steven!