PDA

View Full Version : qobject_cast<T> in shared libraries...



seim
30th December 2008, 17:23
Hi,
I have a question for advanced users or Qt developers..
As far as I can imagine, the qobject_cast provides a safe cast in the QObject* inheritance hierarchy where each node uses Q_OBJECT macro.
Dynamic_cast does practicly the same, but needs RTTI. RTTI isn't always available (for example for objects shared cross two shared libraries in windows, sometimes also in linux as I realized after some tests (ABI)..)

I want to have an interface implemented in one shared library that is used in another shared library... Each interface is managed by PluginManager (singleton object) as an QObject*. Lets have an example:

class IObjekt2 : public QObject
{
Q_OBJECT
protected:
int m_id;
public:
IObjekt2( int id, QObject *parent = 0 ) : QObject(parent), m_id(id) {}
virtual ~IObjekt2() {}
virtual int id() = 0;
};

implementation in one plugin (s.l.):
class Objekt2 : public IObjekt2
{
Q_OBJECT
public:
Objekt2( int id, QObject *parent = 0 ) : IObjekt2(id, parent) {}
~Objekt2() {}
int id() { return m_id; }
};

If we write in the plugin that defines Objekt2:

QObject *o = new Objekt2(199);
IObjekt2 *obj = qobject_cast<IObjekt2 *>(o);
The "obj" will have correct pointer.

But if we use the "o" instance in second plugin, and try to write

IObjekt2 *obj = qobject_cast<IObjekt2 *>(o);
the result is 0 pointer.

I found the reason in:

QObject *QMetaObject::cast(QObject *obj) const
{
if (obj) {
const QMetaObject *m = obj->metaObject();
do {
if (m == this)
return const_cast<QObject*>(obj);
} while ((m = m->d.superdata));
}
return 0;
}

because qobject_cast is practicaly defined as:

template <class T>
inline T qobject_cast(QObject *object)
{
return static_cast<T>(reinterpret_cast<T>(0)->staticMetaObject.cast(object));
}

What is important, MOC transforms Q_OBJECT macro to declaration of two static objects describing our class (one of the is const QMetaObject*staticMetaObject) and some methods.., and qobject_cast uses the "staticMetaObject" defined for our class and calls its cast(QObject*) method. This method goes throw all pointers to static objects that are defined for all classes in the inheritance tree of our class and if some pointer equals to THIS (= pointer to static object describing our class), it knows that QObject* can be static_casted to IObjekt2* without any problems....

Now the question...

Shouldn't there be something like:

template <class T> T my_cast( QObject *object )
{
if (object)
{
const QMetaObject *m = object->metaObject();
const QMetaObject *class_m = reinterpret_cast<T>(0)->staticMetaObject.d.superdata;
do {
if (m == class_m)
return static_cast<T>(object);
} while ((m = m->d.superdata));
}
return (T)0;
}
that compares SUPERDATAs and not THIS pointer (pointer to static data)? The casting cross shared libraries is functional now... Isn't the example with SUPERDATA more general than the previous version?

thanks for your opinion...

The Storm
30th December 2008, 17:54
I personally think that you should report your suggestion to the Qt guys, it will be nice to have it in Qt 4.5. :)

caduel
30th December 2008, 18:59
This (known) "issue" (or feature, if you want; probably this implementation is faster and was chosen with purpose) can be worked around by having the common base class put inside a lib that both "parties" link against. That way there will be only one such metaobject and the qobject_cast will not fail.

Still, reporting it to the trolls won't hurt.

HTH
PS: There are (some) compilers that internally use a string-compare for dynamic_cast, too.
g++, however, uses (for efficiency reasons - again) a pointer comparison of the internal rtti object. Thus, dynamic_cast may (and with g++ does) suffer from across shared library issues, too.

seim
30th December 2008, 19:29
Thanks for answer,


This (known) "issue" (or feature, if you want; probably this implementation is faster and was chosen with purpose) can be worked around by having the common base class put inside a lib that both "parties" link against. That way there will be only one such metaobject and the qobject_cast will not fail.
I thougth, that this common base class can be also QObject, because both sides (plugins) link against Qt libraries. Or am I wrong?

caduel
30th December 2008, 22:44
No. (Only if you try the rather pointless qobject_cast<Qbject*>(...) ;-)

If you do a qobject_cast<T*>(someObj) then the definition of T has to be in a common library. You must not have the header of T added in two .pro files' HEADERS-sections. Otherwise the Q_OBJECT macro will be moced and compiled multiple times. This would lead to multiple instances of the metaobject and the problem you noted.
If you put the definition of T into a lib there will be only one metaobject (the one defined by the lib) and the qobject_cast will work as expected.

HTH

wysota
31st December 2008, 02:03
Shouldn't there be something like:
(...)
that compares SUPERDATAs and not THIS pointer (pointer to static data)?
The cast compares the static meta object of a class it expects with the actual meta object of the object you pass to it.


The casting cross shared libraries is functional now... Isn't the example with SUPERDATA more general than the previous version?

qobject_cast across libraries works :) If you want to cast type A to type B you have to know both types when you make the cast. qobject_cast works because the inheritance data is stored in a "common base class" of all objects - QMetaObject tied to QObject. With dynamic_cast "castable" classes need to be known while compiling the actual class.

So as far as I understand your issue, the problem is not with qobject_cast but actually with the way you make the cast. If qobject_cast hadn't worked across library boundaries, Designer wouldn't be able to handle custom widgets and the plugin infrastructure in Qt wouldn't work at all. If you use GCC (on Unix, it doesn't work with MinGW), you can use some switch that will enable resolving symbols in plugins against the main application which will allow you to define the interface in the main application only without actually referencing it anywhere in the plugin (which I understand currently poses the problem).



By the way, comparing pointers to meta-objects is fine. Static objects have addresses too so that's not a problem.

seim
31st December 2008, 02:55
You must not have the header of T added in two .pro files' HEADERS-sections. Otherwise the Q_OBJECT macro will be moced and compiled multiple times. This would lead to multiple instances of the metaobject and the problem you noted.

Thank you, this is the key! Interface header file was included in both plugin projects and that led to 2x different static objects for "one class". Shame on me :rolleyes:

seim
5th January 2009, 18:23
One more thing as a solution given by B.Poulain:

"The problem come from the declaration of the
class IObject without the macro Q_DECLARE_INTERFACE. This macro should
be used for all objects exported outside the plugin.
If you add it, everything works fine.

If you look at the declaration of Q_DECLARE_INTERFACE, it use a slightly
different approach to make qobject_cast working across library but
without being dependant of the linker."

The IObject header file has to be added in both .pro files.

Thank you all, for your posts.

:p