How to safely cast C union types / use pointer_from_objref?

I have a function, that accepts a pointer to a Julia allocated object, fills it with data, and returns it. I then want to cast that object to another type and do stuff with it. How can I best do that without risking the GC eating the memory allocated for the original object?

Code example:

# Allocate an empty struct in Julia
event = SDL.Event(undef)

# Get C to populate that struct
SDL.PollEvent(Ref(event))

if (event.type == SDL.WindowEvent)
    windowevent = unsafe_load( Ptr{SDL.WindowEvent}(pointer_from_objref(event)) )
    # I think this next line is unsafe.
    return windowevent
end

My questions:

  1. When is event allowed to be garbage collected?
  2. Can I safely return windowevent here? (from the pointer_from_objref docs, I don’t think so)
  3. Is there any way to tell Julia that I want windowevent to own event’s memory / extend its lifetime?

The approaches I’ve come up with are:

  1. Accept a callback and use do syntax to make it mostly okay
  2. Take a copy of the loaded windowevent
  3. Define new structs for each event type that keep a reference to their data (sounds like a lot of work)

Here’s how I’m imagining implementing option 1:

function pollevent(f)
    event = SDL.Event(undef)
    GC.@preserve event begin
        SDL.PollEvent(event)
        # cast event to the right type
        # ...
       
        # give the casted object to f
        f(event)
    end
end

# Use example
pollevent() do e
    # do something
end

It doesn’t have to exist, so never or before any allocation.

Returning is fine. The load that creates it is not.

No and it’s not owning that memory anyway.

You are doing that already.

It’s also unclear what this is trying to do. In particular, it’s unclear what the Ref is doing here. The Ref is not creating anything magic about the event calling it here is the same as calling it in PollEvent. If event is a mutable object and the Ref is just what’s needed to call the C function, you should just move where the Ref call is. If event is a immutable object, this will not cause event to be mutated (in this case pointer_from_objref is also completely invalid).

Now without more context, it’s also unclear why you are creating a event rather than a windowevent in the first place.

Thanks for your response.

If event is a mutable object and the Ref is just what’s needed to call the C function, you should just move where the Ref call is.

Yes, PollEvent is a C function that accepts a pointer, Ref is giving me a pointer. I’ll move it up:

event = Ref(SDL.Event(undef))

Now without more context, it’s also unclear why you are creating a event rather than a windowevent in the first place.

The Event is a C union type of a bunch of more specific events. In this context I want to return a WindowEvent if the type field indicates it is a WindowEvent; or a KeyboardEvent if; or a MouseEvent; etc.

I don’t know until I look at the object at run-time what type it should be.

immutable

If SDL.Event is an immutable struct and I make a Ref to it, is pointer_from_objref okay to be applied to the reference?

Ref does NOT give you a pointer. It is, though, the standard mutable container for a immutable object so that you can get the return value. In that case you must accss the Ref after the call. Calling SDL.PollEvent(Ref(event)) will do nothing to event.

The Ref is not a “reference” and you don’t get a Ref “to” the immutable object in any sense that you might be thinking about it. What you’ve created by calling Ref is not very different from a single element array of the object, just more efficient and with a few convinient methods defined for it to make it easier to use when dealing with C. There’s very little special about Ref apart from ccall handling of it.

Back to the code, doing pointer_from_objref on the Ref is OK, though you do need to GC.@preserve the input argument to pointer_from_objref for as long as you use the returnend pointer to access the underlaying memory.

@ColinCaine The following implementation of sfEvent might be of interest to you. It’s from SFML library, but SDL has a similar design for its event union.

By doing this, you could use it just like a C union, for example,

BTW, @jonathanBieler maintains an SDL2 wrapper here.

@yuyichao

Thanks for the clarifications.

Making a Ref from the event later does seem to work fine, but I’ll change it as you suggest.

Back to the code, doing pointer_from_objref on the Ref is OK, though you do need to GC.@preserve the input argument to pointer_from_objref for as long as you use the returnend pointer to access the underlaying memory.

To be totally clear, I’m safe to not preserve the event once unsafe_load has returned?

@Gnimuc

Thanks for those links. Yes, I know about Jonathan’s wrapper already, I started work on my own wrapper to address some minor issues in their one. Maybe my experiment and Jonathan’s project will merge at some point.

I’m not sure I want to overload getproperty like that, but, that was something else I was considering and it’s useful to see it done :slight_smile: Doesn’t it mean that you potentially make a copy of the event for every propery access, though?

Mostly unrelated: I thought that SFML and GLFW didn’t handle input, because both of them leave that off the top-level description. But they do! They consider that to be part of the Window handling. Woops!

As long as you hahvae access to the Ref it’ll work. Or if event is mutable it is also fine.

Yes.

1 Like

Maybe it’s a good idea to also implement getproperty methods for Ref{Event}:

@inline function unpack_unionref(::Type{T}, objref::Ref{S}) where {T,S}
    GC.@preserve objref  unsafe_load(Ptr{T}(Base.unsafe_convert(Ptr{Cvoid}, objref)))
end

function Base.getproperty(objref::Ref{sfEvent}, sym::Symbol)
    if sym === :type
        return getfield(objref, :type)
    elseif sym === :size
        return unpack_unionref(sfSizeEvent, objref)
  ......
    end
end
1 Like