"Old Style" Callbacks In QT4 (commandline tester)
Posted 12-12-2012 at 08:25 AM by rainbowsally
Tags c++, callback, compiling, computer mad science, qt4
What would be involved in creating a generic callback that we could control ourselves, rather than using QTs rather complicated system?
How about a windows style system with a windproc to handle messages?
QT's API is better than windows in that you can see how things are done, especially if you have the C++ sources. It's as visible as you can stand! ;-)
The Windows API is better than QT's only in one respect, and that is that the type safety is somewhat intuitive rather than hard-core, so that the API is equally accessible from assembler to C++. The parameters are the hwnd, which is an object pointer. The msg is a jump table offset or an easily filtered case switch. And the wparam/lparam combo is great! It can be interpreted as either using a ByVal parameter or a ByRef parameter. It can be an argc/argv combination. It can be additional id's and pointers to a struct or other object. And all of these possibilities is implied in the expected values. You don't expect a 'string' in an wparam field, for example. It would lose precision (and set off a compiler error).It's the lparam, because 'longs' are as wide as pointers, which a string is (at least in low level languages).
Enums in both are just about equally annoying. In Windows you can easily differentiate between a function and a flag or enum but in QT they all look like functions. On the other hand, you CAN'T pass a bad enum to a qt class due to the sometimes excessive type safety of C++.
So...
Let's model the wndproc and call it a 'handler' as do most other oop systems and play with a simplified windows-like api for QT.
[This will be a commanline test, so we hope you can get to a terminal . in your linux. These newer Linuxes are blindly following Windows over a cliff in this regard but NOT implementing the nifty intuitive application interface. -rs]
First we need to know what signals and slots are.
Well we can compile with '--save-temps' in the CFLAGS and see that most of the macros are defined to be noops. So things like 'emit' and 'public signal:' and 'public slots:' are only used by qt's moc (meta object converter) which parses the files to generate the moc_*.cpp files. More on that at the bottom of this blog entry.
However, Q_OBJECT is an actual definition, and so is connect().
We can look a Q_OBJECT later but let's see what 'connect(...)' does right now.
file: src/main.cpp
purpose: source file
Above, we see connect used to connect a QObject and a string and for the SIGNAL connection (wait... I'll explain about the "string" in a sec) and another QObject and string for the SLOT.
That these macros generate strings can be verified by using connect(1,1,1,1) to force an error and looking at the 'candidates'.
Here's the prototype of one of the candidates, and the other is bascially the same.
This call is defined in 'qobject.h' and is a member of the QObject class and it's 'static' so we can use without having an object of the class to call it with).
And so now we know that the signal and slot are 'strings'. But we don't know what strings they are, because the macros dress them up a bit.
Let's put together a tester now and do a kinda messy callback dispatcher but one that at least works and doesn't have to deal with all the locks, unlocks, and funkiness that QT uses behind the scenes to make QT thread safe, and whatever else they need to do.
Here's an mc2.def which you might be able to figure out for a makefile if you don't have or don't want the mc2 application available here at the blog. This creates a makefile that's only about 3.7K.
file: mc2.def
purpose: source file
Here's a first cut at a dispatch class. This follows the windows API fairly closely.
file: src/dispatch.h
purpose: source file
And the implementation code...
file: src/dispatch.cpp
purpose: source file
Here's a test function with all the parameters predefined so we can see what they are in order to verify that the same data gets received. By the way, the QT internal metaobject call for this is ugly as sin, but it works and that's all that matters at this point.
file: src/sigslot.cpp
purpose: source file
Here's the header which defines a slot to set a value in the widget (in case it's needed) and a signal that calls the generic extern "C" windows style callback which can be declared in any class that derives from QObject and can be handled by any class or non-class that implements a dispatcher.
file: src/sigslot.h
purpose: source file
Under the hood:
Here's the innards of that signal above, implemented in the moc_signal.cpp file. This is just a snippet, not a file to copy.
As you can see, QT uses a lot of void*'s too. :-) Good enough for them...
But their call is a bit different from ours.
The first param is again the caller's object pointer ('this').
The second is a pointer to a metaObject, a linked list of methods used by all of the inherited classes. It's used to compute the aggregate id number in the current class.
In QT, when the 'id' is looked up each ancestor class subtracts it's number of methods from this id and returns the resulting value whether or not it executes the call.
When the id is less than 0, each class ignores it and just tosses it out to the next in the list until every class in the chain has seen the id.
Confused? Understandable. Here's the low down.
Say you have an method id of 5 and you pass it to a chain of metaobjects that each have three methods, the first will subtract it's three and pass the resulting '2' to the next class. 2 is within this one's method range (0 - 2) so it executes it's method #2 and subtracts the number of methods it implements from the id and passes the resulting (2 - 3 =) '-1' to to the next class in the list. All the rest of the classes ignore the value and just pass it along until the end of the chain is reached.
The third parameter is unknown here. I've never seen anything but a zero in that position so far but it's name in the qt headers is 'local_signal_index'.
And the fourth parameter, like ours, is a pointer to a parameter list which in qt's case is a list of args that will get unpacked and cast back to the correct types at for a normal call by the receiver's meta definitions which are all in the moc_ files.
Here's the Dispatch class's meta call and how it unpacks the args to do the C++ call from a method id and a generic pointer list (void**).
The moc files are are generated by the makefile. You'll need the qt4 development package in order to play with this and the mc2 makefile creator is recommended over qt's qmake so you can really monkey with the build and see what's going on.
http://www.linuxquestions.org/questi...support-34783/
But if you are handy with makefiles and understand what moc does you should be able to get this working on your own with just the hints in the mc2.def to guide you.
[The libQT files in the mc2 download have been recently updated and the installer has been spot-checked. 3.0.12 had a couple probs, one old one and one new one. -rs]
The Computer Mad Science Team.
:-)
How about a windows style system with a windproc to handle messages?
QT's API is better than windows in that you can see how things are done, especially if you have the C++ sources. It's as visible as you can stand! ;-)
The Windows API is better than QT's only in one respect, and that is that the type safety is somewhat intuitive rather than hard-core, so that the API is equally accessible from assembler to C++. The parameters are the hwnd, which is an object pointer. The msg is a jump table offset or an easily filtered case switch. And the wparam/lparam combo is great! It can be interpreted as either using a ByVal parameter or a ByRef parameter. It can be an argc/argv combination. It can be additional id's and pointers to a struct or other object. And all of these possibilities is implied in the expected values. You don't expect a 'string' in an wparam field, for example. It would lose precision (and set off a compiler error).It's the lparam, because 'longs' are as wide as pointers, which a string is (at least in low level languages).
Enums in both are just about equally annoying. In Windows you can easily differentiate between a function and a flag or enum but in QT they all look like functions. On the other hand, you CAN'T pass a bad enum to a qt class due to the sometimes excessive type safety of C++.
So...
Let's model the wndproc and call it a 'handler' as do most other oop systems and play with a simplified windows-like api for QT.
[This will be a commanline test, so we hope you can get to a terminal . in your linux. These newer Linuxes are blindly following Windows over a cliff in this regard but NOT implementing the nifty intuitive application interface. -rs]
First we need to know what signals and slots are.
Well we can compile with '--save-temps' in the CFLAGS and see that most of the macros are defined to be noops. So things like 'emit' and 'public signal:' and 'public slots:' are only used by qt's moc (meta object converter) which parses the files to generate the moc_*.cpp files. More on that at the bottom of this blog entry.
However, Q_OBJECT is an actual definition, and so is connect().
We can look a Q_OBJECT later but let's see what 'connect(...)' does right now.
file: src/main.cpp
purpose: source file
Code:
// main.cpp -- skeleton created by new.main #include <stdio.h> #include <malloc.h> #include <string.h> #include <QApplication> #include <QtGui> // connect() #include "sigslot.h" #include "dispatch.h" void dbg(){} // for a non-moving breakpoint // global enums, atoms, (string id?) addresses, or name hash id's enum { TEST_PRINT_STRING = 12 }; extern "C" { // a message handler for our app long main_wndproc(void* hwnd, uint msg, int wparam, long lparam) { printf( "\n" "---------------------------\n" "Dispatch received:\n" " Obj = %p\n" // o " Msg = 0x%X\n" // msg " Wparam = %d\n" // wparam " Lparam = 0x%lX\n" // lparam "---------------------------\n" "\n" , hwnd, msg, wparam, lparam); // in sig slot msg channel if((msg >> 16) == SS_MSG) { // Especially in-channel a case switch here MAY // optimize the cases to execute faster. switch(msg) { case SS_INT_RETURN: { int rval = wparam; printf("Msg 0x%X = SS_INT_RETURN\n", msg); printf("Getting a copy of integer value returned\n"); printf("Got '%d'\n", rval); return true; // handled } case SS_STRING_RETURN: { // local vars here as needed printf("Msg 0x%X = SS_STRING_RETURN\n", msg); printf("string return not implemented\n"); return true; // it's handled, though not implemented. } case SS_LIST_RETURN: { // local vars here as needed printf("Msg 0x%X = SS_LIST_RETURN\n", msg); printf("list return not implemented\n"); return true; // it's handled, though not implemented. } case SS_POINTER_RETURN: { // local vars here as needed printf("Msg 0x%X = SS_POINTER_RETURN\n", msg); printf("pointer return not implemented\n"); return true; // it's handled, though not implemented. } } } // other including sig slot channel extensions switch(msg) { case TEST_PRINT_STRING: printf("Message : 12 = TEST_PRINT_STRING\n"); printf("String : %s\n", (char*)lparam); return true; default: printf("I don't recognize message '0x%X'\n", msg); } return default_handler(hwnd, msg, wparam, lparam); } } // extern "C" int main(int argc, char** argv) { dbg(); QApplication app(argc, argv); Dispatch test; SigSlot sigslot; // can be added any time, usually before events start running add_handler(main_wndproc); QObject::connect( &sigslot, SIGNAL(sendMessage(void*,uint, int, long)), &test, SLOT(onSentMessage(void*,uint, int, long)) ); // add routines here sigslot.setValue(20); // for this test we only need to process events already queued, // then leave. So we don't use app.exec() here. app.processEvents(); return 0; }
Above, we see connect used to connect a QObject and a string and for the SIGNAL connection (wait... I'll explain about the "string" in a sec) and another QObject and string for the SLOT.
That these macros generate strings can be verified by using connect(1,1,1,1) to force an error and looking at the 'candidates'.
Here's the prototype of one of the candidates, and the other is bascially the same.
Code:
static bool connect( const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType = <default>);
And so now we know that the signal and slot are 'strings'. But we don't know what strings they are, because the macros dress them up a bit.
Let's put together a tester now and do a kinda messy callback dispatcher but one that at least works and doesn't have to deal with all the locks, unlocks, and funkiness that QT uses behind the scenes to make QT thread safe, and whatever else they need to do.
Here's an mc2.def which you might be able to figure out for a makefile if you don't have or don't want the mc2 application available here at the blog. This creates a makefile that's only about 3.7K.
file: mc2.def
purpose: source file
Code:
## stripped down from 'mc2 -fetch qt4' + EXT_ALL = moc # this CAN use the libLQ-qt lib, but doesn't PREFIX = $(HOME)/usr32 OUTNAME = sigslot EXT_ALL = src/moc_done moc announce BINDIR = . SRCDIR = src OBJDIR = o COMPILE = g++ -c #CFLAGS = -Wall -W -g3 CFLAGS = -Wall -W -O2 INCLUDE = -I$(PREFIX)/include -I$(SRCDIR) -I/usr/include -I/usr/include/Qt -I/usr/include/QtGui -I /usr/include/QtCore LINK = g++ LDFLAGS = -lQtGui -lQtCore -lLQ-qt LIB = -L/usr/lib -L$(PREFIX)/lib ################################################################ ## User Defined Targets clean: @rm -f $(MAIN) $(OBJ)-f *~ */*~ */*/*~ */*/*/*~ distclean: clean @rm -f src/moc_* src/ui_* src/moc_done force: # used to force execution announce: force @echo "------------------------------------" @echo "Creating sigslot in curent directory" @echo "------------------------------------" moc: src/moc_sigslot.cpp src/moc_dispatch.cpp @echo "This is a flag that the moc files exist" > src/moc_done src/moc_sigslot.cpp: src/sigslot.h cd src && moc sigslot.h > moc_sigslot.cpp src/moc_dispatch.cpp: src/dispatch.h cd src && moc dispatch.h > moc_dispatch.cpp src/moc_done: @(make moc && make update) > /dev/null 2>&1 @echo "####################################################" @echo "The moc files need to be rebuilt afer being cleaned" @echo "out by distclean. (Am rebuilding them now)" @echo @echo "Pls repeat the previous operation to continue." @echo "####################################################" @exit 1 2>/dev/null
Here's a first cut at a dispatch class. This follows the windows API fairly closely.
file: src/dispatch.h
purpose: source file
Code:
// dispatch.h - template created with new.header #ifndef dispatch_h #define dispatch_h #include <QObject> extern "C" { // wndproc type, returns non-zero if handled typedef long wndproc(void* hwnd, uint msg, int wparam, long lparam); // used to call previous in list long default_handler(void* hwnd, uint msg, int wparam, long lparam); // add to end of list void add_handler(wndproc* proc); // remove specific handler (only first encountered) void remove_handler(wndproc* proc); // todo, not implemented } // extern "C" // receiver, win/qt-compatible, but this qt version doesn't // give us control of the message loop. class Dispatch : public QObject { Q_OBJECT public slots: void onSentMessage(void* hwnd, uint msg, int wparam, long lparam); }; #endif // dispatch_h
file: src/dispatch.cpp
purpose: source file
Code:
#include "dispatch.h" #define UNUSED(x) x=x extern "C" { // used to call previous in list long default_handler(void* hwnd, uint msg, int wparam, long lparam); // terminate the checks long root_handler(void* hwnd, uint msg, int wparam, long lparam) { UNUSED(hwnd); UNUSED(msg); UNUSED(wparam); UNUSED(lparam); return true; } static wndproc* handlers[1024]; // not a linked list in this version static int last_handler; static int handler_idx; // add to end of list void add_handler(wndproc* proc) { if(!last_handler) handlers[last_handler++] = root_handler; handlers[last_handler++] = proc; } // remove specific handler (only first encountered) void remove_handler(wndproc* proc); // todo long default_handler(void* hwnd, uint msg, int wparam, long lparam) { return handlers[--handler_idx](hwnd, msg, wparam, lparam); } } // extern "C" void Dispatch::onSentMessage(void* hwnd, uint msg, int wparam, long lparam) { handler_idx = last_handler; if(!default_handler(hwnd, msg, wparam, lparam)) { // not handled... ignore unless debugging } }
Here's a test function with all the parameters predefined so we can see what they are in order to verify that the same data gets received. By the way, the QT internal metaobject call for this is ugly as sin, but it works and that's all that matters at this point.
file: src/sigslot.cpp
purpose: source file
Code:
#include "sigslot.h" /* This is inefficient, but it's thread safe, I suppose. A more efficient * implementation might enque the message parameters and send them all at once * on a timer tick so the infernal journey through the metaobject call only needs * to be done once. */ void SigSlot::setValue(int value) { if (value != m_value) { m_value = value; // here are the parameters to send to the dispatcher // which have nothing to do with the current value // for this test. void* hwnd = this; uint msg = 12; int wparam = 34; long lparam = (long) "SigSlot::setValue sending data: msg=12, wparam=34, lparam=this string"; // and here's the call emit sendMessage( hwnd, // the caller's object msg, // method selector wparam, // int type lparam); // long or pointer types // and here's a more useful call emit sendMessage( hwnd, // the caller's object SS_INT_RETURN, // method selector value, // int type (return the input above) 0); // long or pointer types } }
Here's the header which defines a slot to set a value in the widget (in case it's needed) and a signal that calls the generic extern "C" windows style callback which can be declared in any class that derives from QObject and can be handled by any class or non-class that implements a dispatcher.
file: src/sigslot.h
purpose: source file
Code:
// sigslot.h - template created with new.header #ifndef sigslot_h #define sigslot_h #include <QObject> #include "dispatch.h" class SigSlot : public QObject { Q_OBJECT public: SigSlot() { m_value = 0; } int value() const { return m_value; } public slots: void setValue(int value); signals: void sendMessage(void* hwnd, uint msg, int wparam, long lparam); private: int m_value; }; #endif // sigslot_h
Under the hood:
Here's the innards of that signal above, implemented in the moc_signal.cpp file. This is just a snippet, not a file to copy.
Code:
// SIGNAL 0 void SigSlot::sendMessage(void * _t1, CLS_MO _t2, long _t3, void * _t4) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)), const_cast<void*>(reinterpret_cast<const void*>(&_t3)), const_cast<void*>(reinterpret_cast<const void*>(&_t4)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); }
But their call is a bit different from ours.
The first param is again the caller's object pointer ('this').
The second is a pointer to a metaObject, a linked list of methods used by all of the inherited classes. It's used to compute the aggregate id number in the current class.
In QT, when the 'id' is looked up each ancestor class subtracts it's number of methods from this id and returns the resulting value whether or not it executes the call.
When the id is less than 0, each class ignores it and just tosses it out to the next in the list until every class in the chain has seen the id.
Confused? Understandable. Here's the low down.
Say you have an method id of 5 and you pass it to a chain of metaobjects that each have three methods, the first will subtract it's three and pass the resulting '2' to the next class. 2 is within this one's method range (0 - 2) so it executes it's method #2 and subtracts the number of methods it implements from the id and passes the resulting (2 - 3 =) '-1' to to the next class in the list. All the rest of the classes ignore the value and just pass it along until the end of the chain is reached.
The third parameter is unknown here. I've never seen anything but a zero in that position so far but it's name in the qt headers is 'local_signal_index'.
And the fourth parameter, like ours, is a pointer to a parameter list which in qt's case is a list of args that will get unpacked and cast back to the correct types at for a normal call by the receiver's meta definitions which are all in the moc_ files.
Here's the Dispatch class's meta call and how it unpacks the args to do the C++ call from a method id and a generic pointer list (void**).
Code:
int Dispatch::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QObject::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { // this is the slot case 0: onSentMessage((*reinterpret_cast< void*(*)>(_a[1])), (*reinterpret_cast< CLS_MO(*)>(_a[2])), (*reinterpret_cast< long(*)>(_a[3])), (*reinterpret_cast< void*(*)>(_a[4]))); break; default: ; } _id -= 1; } return _id; }
http://www.linuxquestions.org/questi...support-34783/
But if you are handy with makefiles and understand what moc does you should be able to get this working on your own with just the hints in the mc2.def to guide you.
[The libQT files in the mc2 download have been recently updated and the installer has been spot-checked. 3.0.12 had a couple probs, one old one and one new one. -rs]
The Computer Mad Science Team.
:-)
Total Comments 0