LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices


Rate this Entry

"Old Style" Callbacks In QT4 (commandline tester)

Posted 12-12-2012 at 08:25 AM by rainbowsally

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
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>);
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
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
And the implementation code...

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);
}
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**).

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;
}
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.

:-)
Posted in Uncategorized
Views 1183 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 07:38 PM.

Main Menu
Advertisement
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration