Closed thiagojedi closed 4 years ago
Yeah, you have to jump through some hoops to be able to pass closured data.
A block comes in two forms in Crystal, closured (referencing stack elements from its definition context) and unclosured. Your first example is unclosured and thus just a static function pointer. The second example is closured and has two pointers, the function pointer and a pointer to a blob that basically contains the data it references. Now passing that to C you can only pass one pointer, the function one, so it would break once you actually invoke that function pointer given it doesn't get invoked with the second pointer for the closure data. Crystal prevents you from doing this mistake by refusing to just strip the closure data pointer.
Many GObject libraries offer function variants that help with this sort of calling convention by allowing to pass a "user data" object alongside the callback. In those cases we can do something like the following: https://github.com/jhass/crystal-gobject/blob/master/src/notify/notification.cr#L67-L81, here I wrap the closure block into a Box
so I can get a simple pointer to the user data. Then I keep a reference to that pointer in a global singleton so it won't become garbage collected and pass the pointer as user data. Then as function I have something that doesn't reference any outside context, so doesn't require a closure itself, that takes the user data, dereferences and unwraps it and just calls the resulting proc object. This API even has a helpful callback for when the the registered callback is removed so we can free the user data (in this remove our reference so the GC can collect it).
Other functions are even API compatible to how the closure data is passed into the function of a closure block, so we can just pass the closure data pointer as user data, keep a reference to it, strip the closure data from our original block and pass its function pointer as callback: https://github.com/jhass/crystal-gobject/blob/master/src/g_object/object.cr#L14-L19
Unfortunately the ActionEntry
APIs seem to support neither of the approaches in a straight forward way. I see some reference to user_data
in the signature description at https://developer.gnome.org/gio/stable/GActionMap.html#GActionEntry, but I guess it's the user_data
that's passed to https://developer.gnome.org/gio/stable/GActionMap.html#g-action-map-add-action-entries so common to all entries in the map. If we abstract away over add_action_entries
to take a list of a custom struct akin to record ActionEntry(name : String, callback : ->)
instead, we could build the action entries in there all pointing to a static function, ship the actual callbacks in the user data and then dispatch based on the signal name, I guess. I don't fully understand the GSimpleAction
API yet. This would disallow multiple action entries with the same name, but I hope that's illegal anyway.
Contributions to make this possible or simpler are always welcome :)
Ok. Got it to work, but not with the ActionMap#add_action_entries
.
Here's how it ended:
def setup_actions
quitAction = Gio::SimpleAction.new("quit", nil)
quitAction.on_activate { self.quit }
add_action(quitAction)
end
The Crystal compiler raises an error in the SimpleAction#on_activate
so I had to patch it.
If you'd like, I can create a PR with the fix, and my simple application as an example.
Yes, please :)
Hey!
I'm trying to use this lib to create a simple Gtk application, but I'm having issues to setup the menu.
Here's the gist: https://gist.github.com/thiagoabreu/39858db3345904249505a41ccf408bd8
The problem I have is with this line:
The second entry raises an Error:
Error: can't set closure as C struct member
Is there something I'm doing wrong?