qobject_cast<T> in shared libraries...
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:
Code:
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
Code:
IObjekt2 *obj = qobject_cast<IObjekt2 *>(o);
the result is 0 pointer.
I found the reason in:
Code:
{
if (obj) {
do {
if (m == this)
return const_cast<QObject*>(obj);
} while ((m = m->d.superdata));
}
return 0;
}
because qobject_cast is practicaly defined as:
Code:
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:
Code:
template <class T> T my_cast
( QObject *object
) {
if (object)
{
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...
Re: qobject_cast<T> in shared libraries...
I personally think that you should report your suggestion to the Qt guys, it will be nice to have it in Qt 4.5. :)
Re: qobject_cast<T> in shared libraries...
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.
Re: qobject_cast<T> in shared libraries...
Thanks for answer,
Quote:
Originally Posted by
caduel
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?
Re: qobject_cast<T> in shared libraries...
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
Re: qobject_cast<T> in shared libraries...
Quote:
Originally Posted by
seim
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.
Quote:
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.
Re: qobject_cast<T> in shared libraries...
Quote:
Originally Posted by
caduel
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:
Re: qobject_cast<T> in shared libraries...
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