[Interest] qobject_cast, static libraries and plugins

Thiago Macieira 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 [1] 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 
target. 

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 
into problems.

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:
[pseudo-code]
	__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:
[pseudo-code]
	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...
Name: signature.asc
Type: application/pgp-signature
Size: 190 bytes
Desc: This is a digitally signed message part.
URL: <http://lists.qt-project.org/pipermail/interest/attachments/20130809/5c9546be/attachment.sig>


More information about the Interest mailing list