[Interest] qobject_cast, static libraries and plugins
thiago.macieira at intel.com
Fri Aug 9 20:53:31 CEST 2013
On sexta-feira, 9 de agosto de 2013 17:47:12, Jan Kundrát wrote:
> Hi, it looks that I cannot use qobject_cast in the following scenario:
> 0) Everything is built with -fPIC
> 1) class Foo: public QObject is defined in a *static* library libCommon
Let me stop here. Before reading the rest, I can guess what you're going to
say: you've linked libCommon into more than one place. If you've done that,
you have two classes called "Foo". Depending on your compilation flags, they
may not be the same class, despite the name. If that's the case, qobject_cast
is correct in not casting through.
Let's see what you're going to say...
> 2) A plugin is statically linked with libCommon and has a function
> returning subclasses of class Foo; these subclasses are compiled into the
> plugin (main app doesn't include their headers)
> 3) Main application is statically linked with libCommon, loads the plugin
> from step 2) at runtime (i.e. from a shared library), calls the plugin's
> function and performs a qobject_cast<Foo*> to see if the returned instance
> is of the correct type
There you go.
> The problem is that qobject_cast returns zero while dynamic_cast works.
> There's something  suggesting that this is expected and that one has to
> move Foo into a shared library so that there's just one instance of a
> QMetaObject. Is that correct, or is the problem somewhere else?
It's correct: you have a problem somewhere else.
I'd say that dynamic_cast working is a fluke. You should not rely on it.
dynamic_cast works by comparing the object's typeinfo (typeid(ptr), which
returns something found in the ptr's vtable) with the typeinfo of the cast's
qobject_cast works by comparing the object's metaobject (ptr->metaObject(),
which is a virtual call [calls a function found in the ptr's vtable] and that
returns something known) with the metaobject of the cast's target.
As you can see, the mechanisms are similar, but subtly different.
Their behaviour would be the same and well-defined if the C++ One Definition
Rule (ODR) was respected. It seems to have broken down in your application-
plugin combination and that's your problem. You can restore it by moving the
Foo class to a shared library.
As I tell people, a plugin system consists of:
ONE shared library
zero or more applications
zero or more plugins
If you remove the one mandatory component (the shared library), you'll run
I should add: the class in question must be well-anchored too: add the export
macro and link the output of moc into the library.
> This is in a code from my GSoC student which contains plenty of new
> features, so I don't have a standalone use case. What I'd like to know is
> whether this is *supposed* to work, or something that I shouldn't be doing
> in the first place.
No, it's not. It might be made to work, if you toggle your compiler and linker
flags enough. It's easier to follow my advice above.
Technical details (I'm going to assume the cross-compiler IA-64 C++ ABI):
Each class containing at least one virtual function (such as your Foo)
contains a hidden member which is a pointer to the vtable. The definition of
that class will emit a symbol called _ZTV3Foo (TV = virtual table).
Among other things, the vtable contains a pointer to the type's typeinfo
structure. It's called _ZTI3Foo (TI = typeinfo). The typeinfo struct contains
at least the type's name and a pointer(s) to the base class(es) typeinfo.
When you call dynamic_cast<Foo *>(ptr), the compiler emits code that does:
__dynamic_cast(ptr, typeid(Foo), typeid(ptr));
Discounting complex hierarchies and virtual bases (which is the case), all
that the code above needs to do is walk up the hierarchy from typeid(ptr)
until it reaches typeid(Foo) or reaches the top. If it finds typeid(Foo), it
can return ptr with the necessary adjustments. If it doesn't, it returns null.
moc adds something similar. We can't add a new generic field to the vtable, but
we can add a virtual function. So Foo's vtable contains a pointer to this
virtual function, QMetaObject *Foo::metaObject() const. That function does one
thing: it returns the Foo's (static) meta object, Foo::staticMetaObject. The
meta object contains at least the type's name and a pointer to the base
class's meta object.
When you call qobject_cast<Foo *>(ptr), you're actually running:
QMetaObject::cast(ptr, &Foo::staticMetaObject, ptr->metaObject())
There are no complex hierarchies nor virtual bases, so all the code above
needs to do is walk up the hierarchy from ptr->metaObject() until it either
reaches &Foo::staticMetaObject or it reaches the root class (QObject). If it
finds &Foo::staticMetaObject, it can simply return ptr (no adjustments are ever
necessary). Otherwise, it returns null.
So, as you can see, it's very similar up to now, only subtly different. And
those subtle differences are causing your problem.
If you follow my recommendation, then all symbols in question will exist in
*exactly* one place: the shared library. The One-Definition Rule will be
respected at link-time, so there will be no problem.
By using a static library, linked into two different ELF modules, the symbols
in question exist in two places: your executable and the plugin. And I'm not
sure all of them got duplicated. That means ODR must be fixed at run-time, by
the dynamic linker. So you need to give it a little help:
* do not use -fvisibility anywhere
* compile the executable with -rdynamic
Since the executable is loaded first, it will always resolve the symbols to
itself. The lack of hidden visibility plus -rdynamic will mean all symbols
will be exposed to the dynamic linker. When the plugin gets loaded, the
dynamic linker will see that some symbols already exist in the executable, and
will resolve to it.
In other words, the combination of flags above effectively turns your executable
into the shared library that the plugins link to. Only that each plugin has a
copy of the shared library inside, wasting disk space and memory.
Thiago Macieira - thiago.macieira (AT) intel.com
Software Architect - Intel Open Source Technology Center
-------------- next part --------------
A non-text attachment was scrubbed...
Size: 190 bytes
Desc: This is a digitally signed message part.
More information about the Interest