Papierkorb / qt5.cr

Qt5 bindings for Crystal, based on Bindgen
Mozilla Public License 2.0
211 stars 20 forks source link

Marshalling polymorphic Crystal wrappers in QObject #36

Open HertzDevil opened 4 years ago

HertzDevil commented 4 years ago

Once a Crystal wrapper type reaches C++, it loses its Crystal type information because only @unwrap remains, which is the pointer to the original C++ instance; something must keep track of the Crystal instance in a C++ instance. QObject, the base class of most virtual classes, doesn't offer any void * instance variable, but it has dynamic properties. So my idea is:

def to_crystal?(_t : MyWidget.class) : MyWidget?
  # `any` shall refer to the receiver itself (same `@unwrap` if valid)
  any = qvariant_to_crystal(self.property("_bindgen_value"))

  # `CrystalAny` itself does not store polymorphic values; attempt every class down the hierarchy
  # if given type argument is a union type, repeat the part for each alternative
  case any.type_id
  # early return
  when Nil.crystal_instance_type_id         then nil

  # the following part shall be macro-ized
  when MyWidget.crystal_instance_type_id    then any.to(MyWidget)
  when Qt::Widget.crystal_instance_type_id  then any.to(Qt::Widget).as?(MyWidget)
  when Qt::Object.crystal_instance_type_id  then any.to(Qt::Object).as?(MyWidget)
  when ::Reference.crystal_instance_type_id then any.to(::Reference).as?(MyWidget)
  when ::Object.crystal_instance_type_id    then any.to(::Object).as?(MyWidget)

  # nothing matches
  else nil
  end
end

#to_crystal works even for descendents of abstract wrapper types. If MyGraphicsItem, Qt::GraphicsItemImpl < Qt::GraphicsItem:

scene : Qt::GraphicsScene
scene.items.each do |item|
  # `item` is a `Qt::GraphicsItemImpl`
  # if `item` is in fact a `MyGraphicsItem`, the first `any.to` will succeed;
  # otherwise, next check will be against `Qt::GraphicsItem`, which can be
  # downcast to `MyGraphicsItem`, so no syntax error occurs
  if my_item = item.to_crystal?(MyGraphicsItem)
    # item.to_unsafe == my_item.to_unsafe
  end
end

This will be helpful whenever a Qt function returns a Qt object where subclassing is expected. It also makes Qt::Variant usable (to be fair I see no problems with Qt's original interface, but more importantly the type shouldn't give the impression that Crystal types are already integrated within the Qt meta-object system).

Any thoughts on this?

Papierkorb commented 4 years ago

I'm not sure if we should go with a QObject-only approach. I currently see two possibilities that'd work for everything:

  1. Sub-class every class that's wrapped and add a variable that'd carry the Crystal object pointer. libGC can handle cycles, so on that front we're set. On the downside, it'd make the generated code fat. Also, it wouldn't work for classes marked as final - Which isn't used in Qt afaik, but in general, I think it'll become more popular with time. I'm not familiar with the modern Crystal memory layout, so I presume we can't frankenstein a Crystal object and the C++ object into something C++ and Crystal can work at once with (This'd make self == @unwrap).
  2. Have a global thread-safe map to go from raw pointer to Crystal object. Easy and would work for everything, however it's hard to detect that an object was destroyed. libGC would allow some kind of hacks into this I gues to get notified when something is removed, however we can't expect to be everything that's passed to Crystal to be libGC allocated. This would however also work for C-only libraries.
HertzDevil commented 4 years ago

SIP is to PyQt5 what Bindgen is to qt5.cr, and it seems they took the second approach: https://github.com/eduardosm/sip-4.19.7/blob/master/siplib/objmap.c

Their wrapper structs look rather fat too, despite Python being dynamically typed (or perhaps that is the reason). I imagine the #to_crystal code would remain largely the same, the major difference being where the CrystalAny is stored.

Regarding Boehm GC: defining #finalize should be the same as using GC_register_finalizer_ignore_self or passing a C++ finalizer function next to UseGC.

Papierkorb commented 4 years ago

Regarding Boehm GC: defining #finalize should be the same as using GC_register_finalizer_ignore_self or passing a C++ finalizer function next to UseGC.

Yup, that'd be fine. However, I'm not sure if this works even for structures that didn't go through Boehms new/malloc().