Qt動態信號與槽2

想整理一下QAxObject動態信號和槽的實現,似乎太困難了,有些無從下手,先隨便寫寫,以後看懂了再繼續

注:Qt5 staging倉庫已經引入一種全新的信號與槽的語法:信號可以和普通的函數、類的普通成員函數、lambda函數連接(而不再侷限於信號函數和槽函數),詳見 信號與槽的新語法(Qt5)

meta object

 

網上關於元對象的解釋已經很多了,所以我們簡單提一下就可以了:

  • 派生自QObject的類可以添加一個 Q_OBJECT 宏
#define Q_OBJECT /
public: /
     Q_OBJECT_CHECK /
     static const QMetaObject staticMetaObject; /
     virtual const QMetaObject *metaObject() const; /
     virtual void *qt_metacast(const char *); /
     QT_TR_FUNCTIONS /
     virtual int qt_metacall(QMetaObject::Call, int, void **); /
private:
  • 運行 moc 對包含該類的文件進行預處理
moc xxx.h -o moc_xxx.cpp
  • 生成的該文件包含我們元對象的所有信息,爲了清楚起見,看一下QMetaObject定義的數據成員部分:
struct QMetaObject
{
...
...
private:
struct { // private data
        const QMetaObject *superdata;
        const char *stringdata;
        const uint *data;
        const void *extradata;
    } d;
}
  • 你可以隨便找一個moc_xxx.cpp文件,會發現裏面一個字符數組和整數數組。對應這兒的stringdata和data

信號與槽

 

正常情況下,moc 生成的文件中包含信號與槽的信息,而每一個信號或槽

  • 有一個對應的字符串(對應函數原型)
  • 有一個索引
  • 有一個函數定義體(信號和槽都是一個普通的函數)

如何工作?

  • 信號和槽以字符串形式傳遞給connect
bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection )
  • connect 從元對象系統查找信號與槽,找到的話,調用QMetaObject::connect建立二者索引之間的聯繫(這是私有函數,我們可以看到這兒涉及到的只是索引值)
bool QMetaObject::connect(const QObject *sender, int signal_index,
                        const QObject *receiver, int method_index,
                        int type = 0, int *types = 0);
  • 信號的發射:信號是一個普通的函數,該函數將調用所有與之關聯的槽函數或信號函數等
void QMetaObject::activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
  • 如何被調用:前面說connect保存了索引值,而根據索引找到函數並調用是通過 qt_metacall 完成的

動態信號與槽

 

僅僅靜態生成信號與槽有些時候是不夠用的,比如QAxObject和QAxidget信號與槽都是動態生成的,再就是QtScript模塊,script定義的信號與槽,也只能是動態的。

Qt Quarterly 中有一篇文章專門介紹動態的信號與槽的實現  Dynamic Signals and Slots

  • 信號與槽的連接:文中使用了connectDynamicSlot 和 connectDynamicSignal ,而不是我們熟悉的 connect
  • 信號與槽的實現:靜態情況下,它們都是普通的函數,然後qt_metacall 根據索引值就可以調用它們;動態情況下,信號和槽是個數可變的,qt_metacall 如何將索引值和信號槽對應起來?信號和槽的實現如何存儲?

 

Q_OBJECT

動態信號槽,我們不能直接包含這個宏,但是這個宏展開後的函數我們還是需要的,由於不能用moc,我們只能自己來實現這個些函數了。最重要的應該是這兩個函數

     virtual const QMetaObject *metaObject() const;
     virtual int qt_metacall(QMetaObject::Call, int, void **); 

在  Dynamic Signals and Slots   一文中,由於引入了自己的connect方法,所以連 metaObject() 這個也不需要的。只剩下一個:

    int DynamicQObject::qt_metacall(QMetaObject::Call call,
                                    int id, void **arguments)
    {
        id = QObject::qt_metacall(call, id, arguments);
        if (id == -1 || call != QMetaObject::InvokeMetaMethod)
            return id;
    
        Q_ASSERT(id < slotList.size());
        slotList[id]->call(arguments);
        return -1;
    }

QAxObject

 

QAxObject 和 QAxWidget 也都是實現的動態的信號和槽。如果我們看Manual的話,會注意到這樣一個警告:

  • Warning : You can subclass QAxObject, but you cannot use the Q_OBJECT macro in the subclass (the generated moc-file will not compile), so you cannot add further signals, slots or properties. This limitation  is due to the metaobject information generated in runtime .

這個概念上似乎很簡單:從COM組件的類型庫中提取出enum、Interface 和 Event 等信息,然後封裝成Qt的enum、信號、槽、屬性,生成並填充metaobject元對象。

QAxObject 實實在在地生成了一個QMetaObject的對象,這是和  Dynamic Signals and Slots   一文中涉及到的方法的最大的不同。

假定已經將信號或槽的信息提取並保存到一個結構體中:

    struct Method {
        Method() : flags(0)
        {}
        QByteArray type;
        QByteArray parameters;
        int flags;
        QByteArray realPrototype;
    };

然後,我們將動態的信號與槽保存到QMap中

QMap<QByteArray, Method> signal_list;
QMap<QByteArray, Method> slot_list;

再然後我們就可以創建一個QMetaObject對象了(非常繁瑣無味的一個東西),

static QMetaObject * GenerateMetaObject(const QMetaObject *parentObject)
{

    QMetaObject *metaobj = new QMetaObject;

    // revision + classname + table + zero terminator
    uint int_data_size = 1+1+2+2+2+2+1;

    int_data_size += signal_list.count() * 5;
    int_data_size += slot_list.count() * 5;

    uint *int_data = new uint[int_data_size];
    int_data[0] = 1; // revision number
    int_data[1] = 0; // classname index
    int_data[2] = 0; // num_classinfo
    int_data[3] = 0; // idx_classinfo
    int_data[4] = signal_list.count() + slot_list.count(); // num_methods
    int_data[5] = 10; // idx_signals
    int_data[6] = 0; // num_properties
    int_data[7] = 0; // idx_properties
    int_data[8] = 0; // num_enums
    int_data[9] = 0; // idx_enums
    int_data[int_data_size - 1] = 0; // eod;

    char null('/0');
    // data + zero-terminator
    QByteArray stringdata("DynamicObject");
    stringdata += null;
    stringdata.reserve(8192);

    uint offset = int_data[5]; //idx_signals

    // each signal in form prototype/0parameters/0type/0tag/0
    for (QMap<QByteArray, Method>::ConstIterator it = signal_list.begin(); it != signal_list.end(); ++it) {
        QByteArray prototype(QMetaObject::normalizedSignature(it.key()));
        QByteArray type(it.value().type);
        QByteArray parameters(it.value().parameters);
        QByteArray tag;
        int flags = it.value().flags;

        int_data[offset++] = stringdata.length();
        stringdata += prototype;
        stringdata += null;
        int_data[offset++] = stringdata.length();
        stringdata += parameters;
        stringdata += null;
        int_data[offset++] = stringdata.length();
        stringdata += type;
        stringdata += null;
        int_data[offset++] = stringdata.length();
        stringdata += tag;
        stringdata += null;
        int_data[offset++] = flags;
    }

    // each slot in form prototype/0parameters/0type/0tag/0
    for (QMap<QByteArray, Method>::ConstIterator it = slot_list.begin(); it != slot_list.end(); ++it) {
        QByteArray prototype(QMetaObject::normalizedSignature(it.key()));
        QByteArray type(it.value().type);
        QByteArray parameters(it.value().parameters);
        QByteArray tag;
        int flags = it.value().flags;

        int_data[offset++] = stringdata.length();
        stringdata += prototype;
        stringdata += null;
        int_data[offset++] = stringdata.length();
        stringdata += parameters;
        stringdata += null;
        int_data[offset++] = stringdata.length();
        stringdata += type;
        stringdata += null;
        int_data[offset++] = stringdata.length();
        stringdata += tag;
        stringdata += null;
        int_data[offset++] = flags;
    }

    char *string_data = new char[stringdata.length()];
    memset(string_data, 0, sizeof(string_data));
    memcpy(string_data, stringdata, stringdata.length());

    // put the metaobject together
    metaobj->d.data = int_data;
    metaobj->d.extradata = 0;
    metaobj->d.stringdata = string_data;
    metaobj->d.superdata = parentObject;

    return metaobj;
}

 

只有動態生成了QMetaObject對象,QObject::connect 才能用到它上面。上面這個東西 revision number 採用的1(Qt 4.8 moc生成的文件中已經是6了),且僅僅填充了信號與槽,但看起來還是相當困難。

博客原文:http://blog.csdn.net/dbzhang800/article/details/6540089

博文索引  持續更新中。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章