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
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.
- 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.
- 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:
- return g_type_get_qdata(G_TYPE_FROM_INSTANCE(m_object), "_QGLib_QMetaObject");
- Easy to implement, get the QMetaObject and compare class names like the moc-generated qt_metacast does.
- 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.
- 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.
- 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.
- 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.
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.