Using Unmanaged and unsafeBitCast to interface with C

Hi everyone,

I’m having “fun” experimenting with GTK and creating a Swift API for it. This is inspired by existing attempts to do the same: GitHub - TomasLinhart/SwiftGtk: SwiftGtk is an experimental Gtk+ binding for Swift that tries to make usage of Gtk+ pleasant and "Swifty" as much as possible. and GitHub - rhx/SwiftGtk: A Swift wrapper around gtk-3.x and gtk-4.x that is largely auto-generated from gobject-introspection.

I don’t have much experience with C and none with GTK, so this is quite the learning experience. In particular, I’m trying to understand how and why the following code works (this is a simplified version of my Button class):

class Button  {

  let pointer: UnsafeMutablePointer<GtkWidget>

  init(label: String) {
    pointer = gtk_button_new_with_label(label)

    let handler: @convention(c) (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> Void = {
      sender, data in
      let button = unsafeBitCast(data, to: Button.self)
      button.onClick(button)
    }
    g_signal_connect_data(pointer, "clicked", unsafeBitCast(handler, to: GCallback.self), Unmanaged.passUnretained(self).toOpaque(), nil, 0)
  }

  var onClick: (Button) -> Void = { _ in }
}

To explain what’s going on here:

  1. I want the user to be able to set the button’s clicked event handler as a Swift closure. That’s what the onClick property is for.
  2. GTK callbacks are GCallbacks, which are just () -> Void functions. Even though these have an empty parameter list, in reality the parameters can be pretty much anything. GTK relies on undefined behaviour to cast the function pointer to a different type that has the expected parameters.
  3. In the case of a clicked event, these parameters are a pointer to the widget that sent the event (sender) and a void * that can be anything (data).
  4. Because I cannot pass the onClick function directly to GTK’s g_signal_connect_data function, I use a wrapper function (handler) that uses the C calling convention and takes the two expected pointer parameters sender and data.
  5. I use that data pointer to pass the sender object itself to the handler. GTK only gives me a pointer to the GtkButton, but I need the Swift Button.

My questions are:

  1. Does unsafeBitCast(handler, to: GCallback.self) do what I think it does? I’m assuming this just passes a pointer to the handler function, and that the unsafe cast works because GTK will cast it back to a compatible void callback(GtkWidget *sender, void *data) type.
  2. What keeps handler in memory? I haven’t figured this one out yet. It’s a local variable in the initializer, I never store it anywhere, and yet it’s still around by the time it gets called. Does either unsafeBitCast or g_signal_connect_data (which is a C function) affect it?
  3. To pass the Button as an UnsafeMutableRawPointer, I’m using Unmanaged.passUnretained(self).toOpaque(). Is this correct? I’ve seen both passUnretained and passRetained used, but I don’t believe I need a retain here, as the Button will be kept in memory by its parent container.
  4. To read the Button back from the UnsafeMutableRawPointer, I can use either unsafeBitCast(data, to: Button.self) or Unmanaged<Button>.fromOpaque(data).takeUnretainedValue(). Both seem to work. Should I prefer one over the other?

Thanks!

1. Does unsafeBitCast(handler, to: GCallback.self) do what I think it
does?

Yes, although it’s critical to realise that this only works because the functions are @convention (c).

2. What keeps handler in memory?

C function pointers are just pointers to your code section, and thus aren’t reference counted.

3. To pass the Button as an UnsafeMutableRawPointer, I’m using
Unmanaged.passUnretained(self).toOpaque(). Is this correct?

It depends, which is the reason why you’ve seen this done different ways. If you can guarantee that the instance of Button outlasts any possibility that widget might call your handler, it’s OK to use an unretained reference. If not, it’s best to use a retained reference (although you’ll then need a way to release that reference when you’re done).

4. To read the Button back from the UnsafeMutableRawPointer, I can use
either unsafeBitCast(data, to: Button.self) or
Unmanaged<Button>.fromOpaque(data).takeUnretainedValue(). Both seem to
work. Should I prefer one over the other?

Prefer the latter. As a general rule, avoid unsafeBitCast(_:to:) wherever possible.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

6 Likes

Thanks @eskimo, that explains a lot!