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:
- I want the user to be able to set the button’s
clicked
event handler as a Swift closure. That’s what theonClick
property is for. - GTK callbacks are
GCallback
s, 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. - In the case of a
clicked
event, these parameters are a pointer to the widget that sent the event (sender
) and avoid *
that can be anything (data
). - Because I cannot pass the
onClick
function directly to GTK’sg_signal_connect_data
function, I use a wrapper function (handler
) that uses the C calling convention and takes the two expected pointer parameterssender
anddata
. - I use that
data
pointer to pass the sender object itself to the handler. GTK only gives me a pointer to theGtkButton
, but I need the SwiftButton
.
My questions are:
- Does
unsafeBitCast(handler, to: GCallback.self)
do what I think it does? I’m assuming this just passes a pointer to thehandler
function, and that the unsafe cast works because GTK will cast it back to a compatiblevoid callback(GtkWidget *sender, void *data)
type. - 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 eitherunsafeBitCast
org_signal_connect_data
(which is a C function) affect it? - To pass the
Button
as anUnsafeMutableRawPointer
, I’m usingUnmanaged.passUnretained(self).toOpaque()
. Is this correct? I’ve seen bothpassUnretained
andpassRetained
used, but I don’t believe I need a retain here, as theButton
will be kept in memory by its parent container. - To read the
Button
back from theUnsafeMutableRawPointer
, I can use eitherunsafeBitCast(data, to: Button.self)
orUnmanaged<Button>.fromOpaque(data).takeUnretainedValue()
. Both seem to work. Should I prefer one over the other?
Thanks!