Differences between revisions 8 and 9
Revision 8 as of 2012-01-01 19:01:35
Size: 9351
Editor: adsl-48
Comment:
Revision 9 as of 2012-01-01 19:18:03
Size: 9724
Editor: adsl-48
Comment:
Deletions are marked like this. Additions are marked like this.
Line 54: Line 54:
    * Turns out it is supported, but does not blend in very well. The return value is not part of the function signature check + you would have to write something like int r = Q_EMIT foobar(); (or whatever... Q_EMIT is a dummy macro that doesn't do anything at all anyway, but most people think it does magic and probably wouldn't know how to use it in a case like this)

This page contains notes on a research I am doing to see if it is possible to bring QtGLib closer to Qt, make it more generic and more feature-complete, so that it can be used for anything besides GStreamer. The bullets below describe possible solutions to the various problems that have been showstoppers until now.

GValue <-> QVariant

  • Converting GValue to QVariant:
    • For native types: switch() and convert.
    • For Object/Interface/ParamSpec pointers: Store a QGlib::Value into the QVariant and provide template specializations for QVariant::value / qvariant_cast / qVariantValue (qt4 only) that will convert using QGlib::Value::get Screw that. There is no such thing as function template partial specializations.

    • For Enum/Flag types: See below
    • For G_TYPE_POINTER: convert to QMetaType::VoidStar

    • For Boxed types: provide hooks
  • Converting QVariant to GValue:
    • For native types: ditto.
    • If the QVariant contains a QGlib::Value (object/interface/paramspec ptr that comes from a method in the bindings): obvious.
    • For Object/Interface/ParamSpec pointers: Override QVariant::setValue / QVariant::fromValue / qVariantFromValue / qVariantSetValue to store a QGlib::Value directly into the QVariant -> as a result there will be no way to have a QVariant that stores an ObjectPtr directly.

    • For Enum/Flag types: See below
    • For void* / QObject*: store into a G_TYPE_POINTER GValue. Unfortunately we can't do much about other kinds of pointers.
    • For Boxed types: provide hooks
  • Enums/Flags:
    • Method 1: Store them as QGlib::Value inside the QVariant and provide template specializations for all the related QVariant methods (using type traits) which will convert to/from QGlib::Value, just like with the object types.

      • Requires tagging template-wise all the enums/flags that belong to the bindings

      • Requires also handling other kinds of enums/flags by copying QVariant's own implementation of those methods inside the specializations.

    • Method 2: Register qMetaTypeId<enum> <-> GType pairs in global hashtables and convert like native types.

  • Problems:
    • QVariant::convert / QVariant::canConvert will not work correctly. This will need to be documented.
    • QVariant::value will also do conversions for types that a QGlib::Value is stored inside the variant. This is wanted for object types, but maybe not for enums/flags (+ not all conversions may be wanted).
    • QVariant::userType() will always return qMetaTypeId<QGlib::Value> for all object types...

    • Pointer types are a bit problematic... Imagine setting a VideoWidget* to the "widget" property of qwidgetvideosink. The latter expects a G_TYPE_POINTER, but the conversion cannot be made automatically.

  • Bonus:
    • With QVariant method specializations for QGlib::Value, it will be easy to get the original GValue from a QVariant.

GObject <-> QObject

  • A QMetaObject needs to be generated for every GObject subclass.
    • Can be generated either at runtime or at compile time, but the runtime part has to be there anyway so that we can use signals/slots with classes that are unknown at compile time (gst elements...).
    • It can either be generated from GI or by introspecting manually the signals/properties of a class. The advantage of GI is that we can also bind non-signal methods as Q_SLOTS, although I'm not sure if we want this.
    • There is a QMetaObjectBuilder class in various places (including QtCore in qt5), but it's not public. -> Copy until it becomes public.

    • Store the metaobject in the GType quark data, since it is not static data in the binary.
  • We need an ObjectBase superclass that will inherit QObject and will define the moc methods:

    • ObjectBase::metaObject:

      • return g_type_get_qdata(G_TYPE_FROM_INSTANCE(m_object), "_QGLib_QMetaObject");
    • ObjectBase::qt_metacast:

      • Easy to implement, get the QMetaObject and compare class names like the moc-generated qt_metacast does.
    • ObjectBase::qt_metacall:

      • For method (signal) calls, we need to convert the void* array to a GValue array and emit the signal. See below.
      • For property reads/writes, we need to convert GValue to/from QVariant. See above.
      • Ignore anything else.
    • ObjectBase::connectNotify:

      • This is not a moc-generated method, but needs to be overriden so that we know when somebody has connected to a GObject signal.
      • Connect a generic GClosure on the real signal with a marshaller that converts the GValue array to a void* array and calls QMetaObject::activate.
      • BIG POSSIBLE ISSUE HERE: I have never seen QObject signals having return values. We need to check if this is supported

        • Turns out it is supported, but does not blend in very well. The return value is not part of the function signature check + you would have to write something like int r = Q_EMIT foobar(); (or whatever... Q_EMIT is a dummy macro that doesn't do anything at all anyway, but most people think it does magic and probably wouldn't know how to use it in a case like this)

void* <-> GValue

  • QObject::qt_metacall uses a void* array that contains pointers to the actual parameters of the signal. Moc normally knows about their types, so it does a reinterpret_cast<foo*> and writes on them. In our case, we don't know the types, so we have to do some extra work:

    • For native types (int/long/string/etc...): switch() and convert appropriately.
    • Strings will always be mangled as QStrings, so the dillema with const char* and QByteArray is gone.
    • For pointer types: assume void* and convert like the other native types.
    • For Object/Interface/ParamSpec types: The target will always be a RefPointer<SomeObjectClass>, which has constant size because it contains only a SomeObjectClass* member, so we can reinterpret_cast to RefPointer<RefCountedObject*> and be done with it.

      • This requires changing RefPointer to hold a RefCountedObject* instead of a T* because the pointer might be different for interfaces etc due to multiple inheritance

    • For Boxed types: We need to provide conversion hooks.
    • For enums: Enums don't necessarily have a constant size. They are required to have the size of an integer type, but it can be anything. Solution:
      • For generated static bindings, pad all enums to the size of int and use reinterpret_cast<int*> to write in them. This will also need static assertions at compile time to make sure that all the enums used in the bindings have the same size.

      • For enums that are not known at compile time (say an enum in a gst element), we will let QMetaObject present the argument as being a simple int.
    • For flags: QFlag is just an int, so we can use reinterpret_cast<int*> here as well.

      • For flags that are not known at compile time, do the same as with enums.

Callbacks

  • For every function that takes a callback argument, the public bindings interface will need two functions:
    • A template one that takes a functor as an argument
    • A non-template one that takes a functor wrapper as an argument. The functor wrapper will have some standard interface for calling the functor using a void* array for the arguments. See QSlotObject in qt5's qobject.h to understand what I have in mind.
  • The non-template method will internally have a C callback that will take the functor wrapper in its user_data argument, will convert between C types and C++ types and will call the functor.
  • For dynamic bindings (in case we do that), we can assume that the caller already gives us a functor wrapper as a void*.
    • Step 1: cast to functor wrapper
    • Step 2: generate a C callback using libffi and g-i which will in turn call a generic callback marshaller
    • Step 3: pass the functor and the GIFooInfo (whatever is needed) to the marshaller in its user_data argument.
    • Step 4: pass the generated C callback down to the C method call.
    • Now when our callback is called, the generic callback marshaller will be invoked and all that it will need to do is to convert from C types (given in a void* array) to C++ types (which will also be given to the functor in a void* array).
      • This can basically be done the same way it is done for void* <-> GValue conversion, only that there is no GValue. Not sure if enums coming from the C interface can be trusted to have a specific size, though.

Subclassing

One solution might be the following:

  • Make a subclass that inherits from the wrapper class type that we want to inherit in C, i.e. class MyElement : public QGst::Element

  • Force the programmer to use some macro or even write some very small piece of code that will implement the _get_type() function
    • This will internally register the GType using some boilerplate code wrapped up somewhere in QtGLib
    • This will also need to add the interfaces on the GType, which may be possible to do with some template magic on the list of inherited classes
    • This will also have to register the Foo_new function that is currently used for the wrapping system
  • Implement virtual functions by declaring protected Q_SLOTS with a certain name, for example: vfunc_addElement for GstBin's add_element vfunc.

  • The boilerplate class_init function will use the staticMetaObject to introspect the available slots and for every vfunc_* slot that matches some vfunc in C (use GI to find out what is available) it will generate a C function using libffi that will call some dispatcher which in turn will call the slot using the QMetaObject api.
    • This adds the convenience that nothing has to be declared as virtual on the C++ side, since slots are virtual by design.

QtGStreamer/QtGLibResearch (last edited 2012-01-01 19:18:03 by adsl-48)