hugopl/gi-crystal

Generated code expects incorrect type for Gio::AsyncReadyCallback

wildeyedskies opened this issue · 5 comments

If you try to compile this branch of my project, you will get the following error.

https://gitlab.gnome.org/wildeyedskies/wince/-/tree/fix-geoclue-init

In lib/gi-crystal/src/auto/geoclue-2.0/simple.cr:264:76

 264 | LibGeoclue.gclue_simple_new(desktop_id, accuracy_level, cancellable, callback, user_data)
                                                                            ^-------
Error: argument 'callback' of 'LibGeoclue#gclue_simple_new' must be Pointer(Void), not Proc((GObject::Object | Nil), Gio::AsyncResult, Nil)

This results from the generated code function requiring a Gio::AsyncReadyCallback, but the C call expects a pointer for the callback.

However, if you change the C call to instead take the pointer of the proc, you get a error, so I'm not entirely sure what's going on here.

hugopl commented

However, if you change the C call to instead take the pointer of the proc, you get a error, so I'm not entirely sure what's going on here.

Here is the explanation why your attempt to fix didn't work.

Here is the explanation why your attempt to fix didn't work.

I don't think boxing is the reason for this. The signature for Gio.AsyncReadyCallback is actually not a Pointer(Void), so I'm pretty sure the libgen isn't working correctly.

I ran into this while trying to use Gio.Task which generates mismatched bindings between LibGio and Gio.

lib LIbGio
  # generates the alias, but doesn't use it.
  alias AsyncReadyCallback = Pointer(LibGObject::Object), Pointer(LibGio::AsyncResult), Pointer(Void) -> Void

  fun g_task_new(source_object : Pointer(Void), cancellable : Pointer(Void), callback : Void*, callback_data : Pointer(Void)) : Pointer(Void)
end

module Gio
    def self.new(source_object : GObject::Object?, cancellable : Gio::Cancellable?, callback : Gio::AsyncReadyCallback?, callback_data : Pointer(Void)?) : self
      # g_task_new: (Constructor)
      # @source_object: (nullable)
      # @cancellable: (nullable)
      # @callback: (nullable)
      # @callback_data: (nullable)
      # Returns: (transfer full)

      # Generator::NullableArrayPlan
      source_object = if source_object.nil?
                        Pointer(Void).null
                      else
                        source_object.to_unsafe
                      end
      # Generator::NullableArrayPlan
      cancellable = if cancellable.nil?
                      Pointer(Void).null
                    else
                      cancellable.to_unsafe
                    end
      # Generator::NullableArrayPlan
      callback_data = if callback_data.nil?
                        Pointer(Void).null
                      else
                        callback_data.to_unsafe
                      end

      # C call
      _retval = LibGio.g_task_new(source_object, cancellable, callback, callback_data)

      # Return value handling
      Gio::Task.new(_retval, GICrystal::Transfer::Full)
    end
end

There's also a clear problem with trying to call #to_unsafe on a callback_data, which is a Pointer(Void), but that's another issue.

Unfortunately, I'm not sure there are any workarounds that I can find.

Monkey patching isn't possible as you can't monkey patch C lib bindings. Trying to override the binding.yml to use lib_ignore for Gio also proves to be pretty difficult, as it can't conflict with Gio's already present binding.yml

Trying to use shard.override.yml to use a separate gio.crwith a differen't binding.yml also ignores the binding.yml in gio.cr.

info - Pango - No binding config found for Gio-2.0.

Putting binding.yml really anywhere in the project directory causes compile errors for the project like:

Error: can't find file '../../../../../src/bindings/g_lib/error.cr' relative to '/Users/skinnyjames/dsrc/big_editor/lib/gi-crystal/src/auto/g_lib-2.0/g_lib.cr'bi

So it seems that binding.yml is coupled with the lib repo, and the lib repo can't be swapped out.

While the lib is cool, I can't seem to make an app do moderate computation without blocking the UI thread. Definitely open to suggestions. It'd be really great if Lib<Gio/Gtk/whatever> generations were decoupled from their corresponding Gio/Gtk/whatever abstractions, which make it a lot more complicated to work with, especially when there are bugs.

hugopl commented

The Lib* objects must be coupled only with other Lib* form C libraries it depends, any other coupling must be interpreted as a bug. I mean, must be possible to monkey patch things to fix the gi-crystal bugs.

Callback need more <3 for sure. On top of my head it needs:

  • Use Box objects to encapsulate the Crystal Proc.
  • Use ClosureManager to keep the Box objects in memory while the callback can be called.
  • De-register the callback from ClosureManager to avoid memory leaks.

It's possible to mimic a async API in libtest to be able to fix and test these issues in gi-crystal.

I'm trying to use Gio::Subprocess and this issue is now blocking me too 😬.

The problem is that the method doesn't have a DestroyNotify parameter, so GICrystal doesn't recognize it as a callback.

Anyway... I also need to think a better way to map these GObject assync API to Crystal... probably:

proc = Gio::Subprocess.new(argv: ["ls", "-l"], flags: :none)
proc.wait_async(cancelable) do |result|
  ...
end

This would cause GI-Crystal to call g_subprocess_wait_async with GAsyncReadyCallback being a wrapper function that would call the user callback then de-register itself from ClosureManager.