C Binding non-void callback

Hi, I’ve been trying to create bindings to a C library (IUP) but ran into trouble with callbacks, below is a sample of the C lib in Crystal

type Ihandle = Ihandle_ # Ihandle_ is a struct
alias Icallback = (Ihandle*) -> LibC::Int
fun set_callback(ih : Ihandle*, name : LibC::Char*, func : Icallback) : Icallback

A snippet of the Crystal binding is written below (doesn’t work), some background info about the current code:

  • Ihandle is an abstract class
  • I’ve created callback so the shard user doesn’t have to use LibIUP::SomethingHere, I’m not sure if there is a better way to do this.
class Button < Ihandle
  @@box : Pointer(Void)?
  @c_ptr : LibIUP::Ihandle* # created in initializers.

  # skipping initializers and extras

  def action(&block : Ihandle -> Int32)
    callback = ->(ih : LibIUP::Ihandle*){
    # Mainly the code below here is where I'm uncertain.
    @@box = Box.box(callback)
    LibIUP.set_callback(@c_ptr, "ACTION", ->(ih){
      unpacked = Box(typeof(callback)).unbox(???)

I originally tried passing callback for func from which I found out you can’t send closures to C functions.

Any help would be appreciated, if more code samples are needed for context let me know.

Yes, you can only reasonably pass closure procs to a C API that allows you to pass an opaque pointer along. For example GObject provides g_signal_connect_data which invokes the callback with the data argument passed to the original call, alongside the regular arguments. With that you can do something like:

counter = 0
block = -> (object : GObject) do
  counter += 1
  puts "#{counter}: Got #{object}"
boxed_callback = Box.box(block)
# simplified, not the real call
LibGObject.signal_connect_data(object, "foo", boxed_callback, -> (object, data) { 
  Box(GObject ->).unbox(data).call(object) 

The link into the language reference you already provided also explains this.

With the lack of that you may have some luck in being able to have some global state to lookup the original callback from. Say a singleton class CallbackRegistry where you can register the callback in a way that lets you later identify it again, say maybe the object pointer you register the callback for and the event name, so you can have a generic callback handler like ->(ih) { CallbackRegistry.instance.lookup(ih, :button_pressed).call(ih) }.

Of course for something like this you likely want to have a cleanup registry to not leak your callback handlers after they’re not needed anymore. That means to make it viable the library you’re binding to needs to have a hook that notifies you of the deregistration of the callback or the destruction/finalization of the object the callback was registered to, or something like that.

Thanks for the reply, it cleared up how it all works.

Unfortunately, the C callback doesn’t have a data parameter, nor does there seem to be any “hook” for when the object is destroyed that I can use.

I’ll try the projects mailing list in case there is something that I’m not seeing in the docs.

Thanks again.

I’m not sure I understood all that correctly, but you can check out this thread, where I invent a Rube-Goldberg machine to let an application talk to itself through a Unix-Socket.

(using C-lib that takes a callback but has no data-pointer)

Not sure if you want to go there, but maybe it can offer some ideas?

Sorry for the delayed reply.

I’m not sure I understood all that correctly

Was this because of my explanation? I can rewrite it if need be.

While your solution (using Unix sockets) might work, I’m not sure about using them as they are specific to Unix and Unix-like systems while IUP is also used on Windows. Although that’s not currently a problem (as Crystal doesn’t natively work on Windows), I can’t be certain that there is something similar on Windows.

Additionally, with how I see it being used, it’d still require some sort of notification from IUP that the callback isn’t needed so it can be freed from memory.

Passing extra data is not a fixed pattern that has to be done through 2 callback arguments. The point is, having the thing you’re setting the callback on, you need any way to set any data associated with it, and then any way to get to that data, having only the callback arguments that the library provides (here just the Ihandle). It is very apparent here that in both parts you have access to the same Ihandle. So it is indeed possible to use a lookup hashtable (with it as the key) as @jhass suggests. But there may be better ways, that would require learning the library itself in detail. In a glance, I see it has “attributes” which may or may not be a way to store arbitrary data on an object.

Thanks for explaining it some more. I think some of my problem was the uncertainty of how much/what Crystal code could be used in the C callback, and I thought that boxing had to be used as in the overview.

So it is indeed possible to use a lookup hashtable (with it as the key) as @jhass suggests.

I have since implemented this sort of solution. My concern with this was that I couldn’t find any way for the library to notify the binding that the callback (or to a larger extent, the object) isn’t needed anymore. I’ve since found that this is possible, so I don’t see any issues with this solution for now.

But there may be better ways, that would require learning the library itself in detail. In a glance, I see it has “attributes” which may or may not be a way to store arbitrary data on an object.

Before starting this binding I didn’t have any experience with the library itself, so I am aware of the need to learn it, which I am doing as I go and in some free time. If I do find a better alternative I’ll swap to using that. As for using the “attributes” (if you’re curious) they are more-or-less properties for the controls/dialogs (e.g. title, size, visibility, etc.). You can create one with any name you want, however, they only store and return primitive types. Whilst you might be able to store the pointer address for the proc or something, that doesn’t seem like a good idea when compared with a “notification” from the library that memory can be released (this particular one if specifically for language bindings).