PDA

View Full Version : casting result of QWidgetStack->currentWidget()



Moppel
31st December 2006, 19:11
Hi,

I've got the following (pseudo) code:



class A : public QWidget
virutal void foo() = 0;

class B : public class A
void foo(){ bar = 1;}

class C : public class A
void foo(){ bar = m_fooBar;}

========

QStackedWidget stack;
stack.addWidget(b); //b instance of class B
stack.addWidget(c); //c instance of class C

A* a = dynamic_cast<A*>stack.getCurrentWidget();
if(a)
a->foo();
else
// cast failed?


Class A is basically the to be implemented interface for the classes B,C ...

It works on the mac (with gcc v401) but fails on my linux box (with gcc v335).
On the latter I have to use static_cast<> to get it working.
Code and .pro file are identical.

I'm a bit clueless at the moment what is going on.
For dynamic_cast RTTI is needed to be enabled, correct? How would I find out if it wasn't.
What cast is actually recommended here? (sort of asking for best practise)

Thanks,

Moppel

e8johan
1st January 2007, 12:24
By adding the Q_OBJECT macro you can use the qobject_cast<> instead - using Qt's RTTI instead of the compiler's.

Moppel
1st January 2007, 18:06
thanks, but that was the last thing I tried yesterday.
Still, it doesn't work. (see output below)



bool PluginConfig::applyChanges()
{
qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << m_wdgtStack->currentIndex() << m_wdgtStack->count();
ConfigWidget* wdgt = qobject_cast<ConfigWidget*>(m_wdgtStack->currentWidget());
if(wdgt)
return wdgt->applyChanges();
else{
qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "no widget in stack?";
wdgt = static_cast<ConfigWidget*>(m_wdgtStack->widget(m_wdgtStack->currentIndex()));
if(wdgt)
return wdgt->applyChanges();
else
return false;
}
}




configdialog.cpp 73 apply
pluginconfig.cpp 35 applyChanges 0 3
pluginconfig.cpp 40 applyChanges no widget in stack?
alsaconfig.cpp 41 applyChanges


I hadforgotten to include the Q_OBJECT
but after I included it I run a
$>qmake -recursive; make clean; clear; make
just to make sure.

Any other ideas?

I read a bit in "Thinking in C++" and if I got that right, the static_cast<> only allows me to downcast within the hierarchy. That would be alright then.

Moppel
3rd January 2007, 13:52
actually, I had the chance to get hold off a windows box today and tried the code with the mingw version of qt422.
Same result. The qobject_cast doesn't work either :(

gfunk
3rd January 2007, 18:19
I think dynamic_cast can only be used for casting up the hierarchy, so you can't cast from a Widget* to a derived class ptr; only from the derived class ptr to thet Widget*. But not totally sure as I've never [had a reason] to use it... static_cast is probably what you should be using??

caduel
3rd January 2007, 20:05
In order for qobject_cast to work you have to
- derive from QObject (first base class, in case of multipl. inheritance)
- use the Q_OBJECT macro
- put the class declaration in a .h file
- mention the .h file in .pro file (add something like HEADERS += your.h)
(Make sure to check there is a moc-generated file for your class!)

Then the check with qobject_cast will work (at least with gcc3/4 on Linux).

Please do not static_cast unless you know the class *must* be of the type you cast to. qobject_cast (and dynamic_cast) are for *testing* if an object has a certain dynamic type. If you know that it must have that type you can safely use static_cast (just hope you will never change the code and forget that 'unsafe' cast...).

HTH
Christoph

Moppel
5th January 2007, 21:02
I indeed hat forgotten to include one header file into the pro file but this did not solve the problem.

I created two small examples.
The first works like I want it too.
It's basically like I described it in my previous post.



int main( int argc, char **argv )
{
QApplication a(argc, argv);
QStackedWidget stack;
stack.addWidget(new A());
stack.addWidget(new B());

CW *c = qobject_cast<CW*>(stack.currentWidget());
if(c)
c->apply();
else
qDebug("cast not sucessful - pointer is 0 %x",c);

stack.setCurrentIndex(1);

c = dynamic_cast<CW*>(stack.currentWidget());
if(c)
c->apply();
else
qDebug("cast not sucessful - pointer is 0 %x",c);

return 1;
}


CW is the interface (pure virtual) inheriting from QWidget and A and B just implement this interface.
On both platforms (Mac gcc4, Linux gcc3) I get the same result:


apply A called
apply B called

So there is no difference if I cast via dynamic_cast or qobject_cast.

Now I move CW, A and B into a Plugin. (The complete is in the attached cast2.zip)
After doing so I get different outputs:


#Linux gcc3
cast not sucessful - pointer is 0 0
cast not sucessful - pointer is 0 0
#Mac gcc4
cast not sucessful - pointer is 0 0
apply B called


On the Mac(gcc4) the dynamic cast still works which doesn't using gcc3 on linux.
qobject_cast fails on both.

Is there a reason why it must fail? If so I would like to know about. And if not, what did I'm wrong?

I attach the sources of both projects.
cast
Building: qmake; make
Running: ./castt #linux
./cast.app/Contents/MacOS/castt #mac

cast2
Building: qmake -recursive; make
Running: cd bin
./castt #linux
./cast.app/Contents/MacOS/castt #mac

caduel
6th January 2007, 09:30
qobject_cast vs dynamic_cast:

qobject_cast and dyamic_cast in spirit do the same thing in different ways.
qobject_cast uses Qt's meta type system and only works for classes derived from QObject (plus using Q_OBJECT....!).
dynamic_cast needs RTTI and on gcc does not work well over plugin boundaries (see the Qt docs on qobject_cast, see also 'one definition rule' (ODR), or google for plugin + dyanmic_cast).

The safer (albeit a bit slower; the gcc dynamic_cast just compares pointers to the type info - which is why dynamic_cast has the above mentioned problems) and more portable (dynamic_casts implementation varies amongst different compilers) way is to use qobject_cast, I guess, although you can do that only for QObject based classes.

When I am running linux I will give your example a try.

[edit: the original statement that qobject_cast does a string compare is not true for Qt4.2; there are several implementations (other compilers, naturally) of dynamic_cast that use one, however. These should 'work' even across plugins.]

caduel
6th January 2007, 10:27
to your example (cast2):

i) please output pointers with %p and without casting them to unsigned int.
This does not compile on my 64bit machine. And just using %p is so much shorter and clearer.

ii) try using qDebug() << "current" << stack.currentWidget();
instead of qDebug("...");
this makes it much simpler to output Qt objects (widgets, lists, QSize etc)

iii) apart from that your exmaple compiles on my suse10.2 linux (amd64) box and both casts fail.

iv) the (potential) failure of dynamic_cast across plugins is one of the reasons the trolls made their own cast (see the docs to QObject, and there qobject_cast)

v) a test with inherits succeeds for me
i.e. stack.currentWidget()->inherits("CW")
(you'd have to cast to CW* with static_cast then)

vi) so, why does qobject_cast fail anyway:
Well, as inherits (slow) succeeds, we know that the type info on A and CW etc actually is present.
The trolls could have made qobject_cast to work, but for efficiency I guess, qobject_cast traverses the qmetaobject (tree) until it finds a the same (pointer identity) meta object. Then the casts was successful. Otherwise it fails. So, obviously the pointers do not match for your example. Why?

You put, however, cw.h into the HEADERS+= of *both* the plugin and your main. This results in 2 mocs generated for cw.h - each of which contains the static meta object for CW. The problem is - just with dynamic_cast I should think - that your plugin creates the CW object with a reference to its own metaobject for CW and your main tests against the other meta object. (Note that these meta objects a equal but not the same - they are two identical copies with a different identity (i.e. address).)
Removing cw.h from the HEADERS causes a link failure. Both parts need to link against this metaobject - but it should be the very same, not two copies of it (see solution ii).

Solutions:
i) use inherits (rather not, I'd prefer the next one)
ii) do NOT put cw.h (resp. the moc code) inside the plugin but rather into a separated library (say libMYPLUGINBASE.so, that both main and the plugin will link against). This way there will be just one instance of CW's metaobject and qobject_cast should work as intended.

HTH
Christoph

Moppel
7th January 2007, 19:07
Thanks Christoph!

That is a really good and really elaborate answer! A lot food for thought.

Whilst starting to try your solution (ii) I came up with another solution.
I could link the main program with the plugin. Whilst it works it's a bit silly. The plugin will be loaded at the launch of the program only to be loaded via the plugin loader again. And the main would have to be linked against the other plugins, if there is more than one.
So I implemented you solution (ii) and I'll attach both just for completeness.

Also I want to add: With my previous (cast2.zip) approach the equal meta object isn't only loaded with both the main and the plugin but with *every* other plugin loaded.

Still the initial loading gives me some headaches.
I like to run the program with ./bin/<program> after the make whilst developing. On linux I can add the -rpath to the linker not to have to set the LD_LIBRARY_PATH. (I just learned about $ORIGIN which I find very handy). I guess I should the linker flag to an other variable but adding it to LIBS just works.
But on the Mac there isn't an equivalent to -rpath according to Apple's developer site. Either I set
export DYLD_LIBRARY_PATH=`pwd`/bin/plugin
or I use the install_name_tool as described here:
http://doc.trolltech.com/4.2/deployment-mac.html
Now I only need to be able to add some command to the project file which will be issued every time I run a make in the project's root directory (could create a "shortcut script" for make though).

Another thing I haven't solved yet with cast2b.zip is, that whenever I change cw.h main.cpp and every plugin has to be rebuild. At the moment only the library for cw.h is rebuild. Since it's an interface for the plugins I will have to apply the changes to every plugin anyway. And being remembered by the compiler by an error would help very much if I had forgotten to apply the changes to one of the plugins.

Right, my Konquerror just chrashed on my when submitting this post. Normally I copy the complete text into the clipboard before hitting submit. But this time the clipboard only contained the path to the attachments. Grrrr

caduel
8th January 2007, 20:37
i) regarding the 'other thing':
you have to tell your build system that the plugin depends on cw.h; yes, you and I know it, it is obvious from the source... but it is not in the makefile.
(there should be a line like
tmp/moc_a.cpp: ../interface/cw.h \
...
)
You have to tell qmake to do so. use DEPENDPATH (basically set it to include anything under your control, i.e. here set it to be the same like your INCLUDEPATH (if you include /usr/include you get dependencies to that, too. you will not really want that, however). You will see that Makefile will then include the dependencies and calls to make will rebuild both main and the plugins.

ii) linking against the plugin. hmm, what's the point in calling it plugin if you link against it anyway? then it's just a shared lib with some stuff you do not really need. and when you have 2 plugins... you have to decide which of those includes the moc for cw.h, or you're back where you started off.
(and if you do decide, which should it be? you're fellow programmers will not be able to understand that solution later on. keep in mind to keep your solution simple. you yourself will be grateful for that later on.)
'my' solution ii) is basically the same, but it extracts this common thing into a regular shared lib. easy to do. worth it.

iii) actually finding the plugin at runtime.
sorry, I don't know about mac. I can tell you about linux.
linux looks at the following places (in this order)
1) LD_LIBRARY_PATH (unless s-bits are set on the binary!)
2) LD_RUN_PATH (corresponds to rpath setting on the linker; the setting is relevant at compile time, not runtime! the settings is compiled into the binary)
3) your systemwide defaults (etc/ld.so.conf)

If you don't know where the libs will be, you have a (small) problem.
See Trolltechs docs on doc/html/deployment.html (inside the Qt docs dir).

Usually you will want to create a wrapper script to set the environment before starting your app. If you can make sure that the app (or rather: its libs, plugins) are installed to a default location, you can get away without it. But a wrapper script is always a good idea. (You can control/log some additional stuff there.)

Moppel
13th January 2007, 23:07
I noticed that there where a couple of glitches in my example so I attach a new version.

I now had the chance to test this on Windows and I can't get it working.
Tried to figure the problem with listdlls (from SystInternals) and Dependency Walker (as described within the Trolltech documentation) but now I'm really stuck. Missing strace on Win and the error messages are far from elaborate not to say not present at all.
So if anyone could test this (example cast2c) on Windows and can tell where I'm going wrong, I'd really appreciate that. It is just giving me headaches.
It's more an academic urge to get it working on all three platforms but still I wish to figure it out.
It's working on both Linux and Mac OSX. (On the former you will see an error message which is correct and on the latter you really should read the README)

Thanks!