Qt高級——Qt元對象系統(tǒng)源碼解析

Qt高級——Qt元對象系統(tǒng)源碼解析

    基于Qt4.8.6版本

一、Qt元對象系統(tǒng)簡介

1、元對象系統(tǒng)簡介

Qt 的信號槽和屬性系統(tǒng)基于在運行時進行內(nèi)省的能力,所謂內(nèi)省是指面向?qū)ο笳Z言的一種在運行期間查詢對象信息的能力, 比如如果語言具有運行期間檢查對象型別的能力,那么是型別內(nèi)?。╰ype intropection)的,型別內(nèi)省可以用來實施多態(tài)。
C++的內(nèi)省比較有限,僅支持型別內(nèi)省, C++的型別內(nèi)省是通過運行時類型識別(RTTI)(Run-Time Type Information)中的typeid 以及 dynamic_cast關(guān)鍵字來實現(xiàn)的。
Qt拓展了C++的內(nèi)省機制,但并沒有采用C++的RTTI,而是提供了更為強大的元對象(meta object)機制,來實現(xiàn)內(nèi)省機制。基于內(nèi)省機制,可以列出對象的方法和屬性列表,并且能夠獲取有關(guān)對象的所有信息,如參數(shù)類型。如果沒有內(nèi)省機制,QtScript和 QML是難以實現(xiàn)的。
Qt中的元對象系統(tǒng)全稱Meta Object System,是一個基于標準C++的擴展,為Qt提供了信號與槽機制、實時類型信息、動態(tài)屬性系統(tǒng)。元對象系統(tǒng)基于QObject類、Q_OBJECT宏、元對象編譯器MOC實現(xiàn)。
A、QObject 類
作為每一個需要利用元對象系統(tǒng)的類的基類。
B、Q_OBJECT宏
定義在每一個類的私有數(shù)據(jù)段,用來啟用元對象功能,比如動態(tài)屬性、信號和槽。
在一個QObject類或者其派生類中,如果沒有聲明Q_OBJECT宏,那么類的metaobject對象不會被生成,類實例調(diào)用metaObject()返回的就是其父類的metaobject對象,導致的后果是從類的實例獲得的元數(shù)據(jù)其實都是父類的數(shù)據(jù)。因此類所定義和聲明的信號和槽都不能使用,所以,任何從QObject繼承出來的類,無論是否定義聲明了信號、槽和屬性,都應該聲明Q_OBJECT 宏。
C、元對象編譯器MOC (Meta Object Complier),
MOC分析C++源文件,如果發(fā)現(xiàn)在一個頭文件(header file)中包含Q_OBJECT 宏定義,會動態(tài)的生成一個moc_xxxx命名的C++源文件,源文件包含Q_OBJECT的實現(xiàn)代碼,會被編譯、鏈接到類的二進制代碼中,作為類的完整的一部分。

讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領域值得信任、有價值的長期合作伙伴,公司提供的服務項目有:域名注冊、網(wǎng)頁空間、營銷軟件、網(wǎng)站建設、蒲縣網(wǎng)站維護、網(wǎng)站推廣。

2、元對象系統(tǒng)的功能

元對象系統(tǒng)除了提供信號槽機制在對象間進行通訊的功能,還提供了如下功能:
QObject::metaObject() 方法
獲得與一個類相關(guān)聯(lián)的 meta-object
QMetaObject::className() 方法
在運行期間返回一個對象的類名,不需要本地C++編譯器的RTTI(run-time type information)支持
QObject::inherits() 方法
用來判斷生成一個對象類是不是從一個特定的類繼承出來,必須是在QObject類的直接或者間接派生類當中。
QObject::tr() and QObject::trUtf8()
為軟件的國際化翻譯字符串
QObject::setProperty() and QObject::property()
根據(jù)屬性名動態(tài)的設置和獲取屬性值
??使用qobject_cast()方法在QObject類之間提供動態(tài)轉(zhuǎn)換,qobject_cast()方法的功能類似于標準C++的dynamic_cast(),但qobject_cast()不需要RTTI的支持。

3、Q_PROPERTY()的使用

#define Q_PROPERTY(text)

Q_PROPERTY定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC處理。

Q_PROPERTY(type name
            READ getFunction
            [WRITE setFunction]
            [RESET resetFunction]
            [NOTIFY notifySignal]
            [REVISION int]
            [DESIGNABLE bool]
            [SCRIPTABLE bool]
            [STORED bool]
            [USER bool]
            [CONSTANT]
            [FINAL])

Type:屬性的類型
Name:屬性的名稱
READ getFunction:屬性的訪問函數(shù)
WRITE setFunction:屬性的設置函數(shù)
RESET resetFunction:屬性的復位函數(shù)
NOTIFY notifySignal:屬性發(fā)生變化的地方發(fā)射的notifySignal信號
REVISION int:屬性的版本,屬性暴露到QML中
DESIGNABLE bool:屬性在GUI設計器中是否可見,默認為true
SCRIPTABLE bool:屬性是否可以被腳本引擎訪問,默認為true
STORED bool:
USER bool:
CONSTANT:標識屬性的值是常量,值為常量的屬性沒有WRITE、NOTIFY
FINAL:標識屬性不會被派生類覆寫
注意:NOTIFY notifySignal聲明了屬性發(fā)生變化時發(fā)射notifySignal信號,但并沒有實現(xiàn),因此程序員需要在屬性發(fā)生變化的地方發(fā)射notifySignal信號。
Object.h:

#ifndef OBJECT_H
#define OBJECT_H

#include <QObject>
#include <QString>
#include <QDebug>

class Object : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)
    Q_PROPERTY(int score READ score  WRITE setScore NOTIFY scoreChanged)
    Q_CLASSINFO("Author", "Scorpio")
    Q_CLASSINFO("Version", "1.0")
    Q_ENUMS(Level)
protected:
    QString m_name;
    QString m_level;
    int m_age;
    int m_score;
public:
    enum Level
    {
        Basic,
        Middle,
        Advanced
    };
public:
    explicit Object(QString name, QObject *parent = 0):QObject(parent)
    {
        m_name = name;
        setObjectName(m_name);
        connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
        connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
    }

    int age()const
    {
        return m_age;
    }

    void setAge(const int& age)
    {
        m_age = age;
        emit ageChanged(m_age);
    }

    int score()const
    {
        return m_score;
    }

    void setScore(const int& score)
    {
        m_score = score;
        emit scoreChanged(m_score);
    }
signals:
    void ageChanged(int age);
    void scoreChanged(int score);
public slots:

     void onAgeChanged(int age)
     {
         qDebug() << "age changed:" << age;
     }
     void onScoreChanged(int score)
     {
         qDebug() << "score changed:" << score;
     }
};

#endif // OBJECT_H

Main.cpp:

#include <QCoreApplication>
#include "Object.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Object ob("object");

    //設置屬性age
    ob.setProperty("age", QVariant(30));
    qDebug() << "age: " << ob.age();
    qDebug() << "property age: " << ob.property("age").toInt();

    //設置屬性score
    ob.setProperty("score", QVariant(90));
    qDebug() << "score: " << ob.score();
    qDebug() << "property score: " << ob.property("score").toInt();

    //內(nèi)省intropection,運行時查詢對象信息
    qDebug() << "object name: " << ob.objectName();
    qDebug() << "class name: " << ob.metaObject()->className();
    qDebug() << "isWidgetType: " << ob.isWidgetType();
    qDebug() << "inherit: " << ob.inherits("QObject");

    return a.exec();
}

4、Q_INVOKABLE使用

#define Q_INVOKABLE

Q_INVOKABLE定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC識別。
Q_INVOKABLE宏用于定義一個成員函數(shù)可以被元對象系統(tǒng)調(diào)用,Q_INVOKABLE宏必須寫在函數(shù)的返回類型之前。如下:
Q_INVOKABLE void invokableMethod();
invokableMethod()函數(shù)使用了Q_INVOKABLE宏聲明,invokableMethod()函數(shù)會被注冊到元對象系統(tǒng)中,可以使用 QMetaObject::invokeMethod()調(diào)用。
Q_INVOKABLE與QMetaObject::invokeMethod均由元對象系統(tǒng)喚起,在Qt C++/QML混合編程、跨線程編程、Qt Service Framework以及?Qt/ HTML5混合編程以及里廣泛使用。
A、在跨線程編程中的使用
如何調(diào)用駐足在其他線程里的QObject方法呢?Qt提供了一種非常友好而且干凈的解決方案:向事件隊列post一個事件,事件的處理將以調(diào)用所感興趣的方法為主(需要線程有一個正在運行的事件循環(huán))。而觸發(fā)機制的實現(xiàn)是由MOC提供的內(nèi)省方法實現(xiàn)的。因此,只有信號、槽以及被標記成Q_INVOKABLE的方法才能夠被其它線程所觸發(fā)調(diào)用。如果不想通過跨線程的信號、槽這一方法來實現(xiàn)調(diào)用駐足在其他線程里的QObject方法。另一選擇就是將方法聲明為Q_INVOKABLE,并且在另一線程中用invokeMethod喚起。
B、Qt Service Framework
Qt服務框架是Qt Mobility 1.0.2版本推出的,一個服務(service)是一個獨立的組件提供給客戶端(client)定義好的操作??蛻舳丝梢酝ㄟ^服務的名稱,版本號和服務的對象提供的接口來查×××。 查找到服務后,框架啟動服務并返回一個指針。
服務通過插件(plug-ins)來實現(xiàn)。為了避免客戶端依賴某個具體的庫,服務必須繼承自QObject,保證QMetaObject?系統(tǒng)可以用來提供動態(tài)發(fā)現(xiàn)和喚醒服務的能力。要使QmetaObject機制充分的工作,服務必須滿足,其所有的方法都是通過 signal、slot、property或invokable method和Q_INVOKEBLE來實現(xiàn)。

QServiceManager manager;
QObject *storage ;  
storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); 
if(storage)     
    QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt")); 

上述代碼通過service的元對象提供的invokeMethod方法,調(diào)用文件存儲對象的deleteFile() 方法。客戶端不需要知道對象的類型,因此也沒有鏈接到具體的service庫。?當然在服務端的deleteFile方法,一定要被標記為Q_INVOKEBLE,才能夠被元對象系統(tǒng)識別。
Qt服務框架的一個亮點是它支持跨進程通信,服務可以接受遠程進程。在服務管理器上注冊后,進程通過signal、slot、invokable method和property來通信,就像本地對象一樣。服務可以設定為在客戶端間共享,或針對一個客戶端。?在Qt服務框架推出之前,信號、槽以及invokable method僅支持跨線程。 下圖是跨進程的服務/客戶段通信示意圖。invokable method和Q_INVOKEBLE?是跨進城、跨線程對象之間通信的重要利器。
Qt高級——Qt元對象系統(tǒng)源碼解析

二、Qt元對象系統(tǒng)源碼解析

1、Q_OBJECT宏的定義

任何從QObject派生的類都包含自己的元數(shù)據(jù)模型,一般通過宏Q_OBJECT定義。
Q_OBJECT定義在/src/corelib/kernel/Qobjectdefs.h文件中。

#define Q_OBJECT \
public: \
    Q_OBJECT_CHECK \
    static const QMetaObject staticMetaObject; \
    Q_OBJECT_GETSTATICMETAOBJECT \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    QT_TR_FUNCTIONS \
    virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
    Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \
    Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);

QMetaObject類型的靜態(tài)成員變量staticMetaObject是元數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。metaObject,qt_metacast,qt_metacall、qt_static_metacall四個虛函數(shù)由MOC在生成的moc_xxx.cpp文件中實現(xiàn)。metaObject的作用是得到元數(shù)據(jù)表指針;qt_metacast的作用是根據(jù)簽名得到相關(guān)結(jié)構(gòu)的指針,返回void*指針;qt_metacall的作用是查表然后調(diào)用調(diào)用相關(guān)的函數(shù);qt_static_metacall的作用是調(diào)用元方法(信號和槽)。
#define Q_DECL_HIDDEN __attribute__((visibility("hidden")))

2、QMetaObject類型

QMetaObject類定義在/src/corelib/kernel/Qobjectdefs.h文件。

struct Q_CORE_EXPORT QMetaObject
{
  ...
enum Call {
    InvokeMetaMethod,
    ReadProperty,
    WriteProperty,
    ResetProperty,
    QueryPropertyDesignable,
    QueryPropertyScriptable,
    QueryPropertyStored,
    QueryPropertyEditable,
    QueryPropertyUser,
    CreateInstance
};

   int static_metacall(Call, int, void **) const;
   static int metacall(QObject *, Call, int, void **);
  struct { // private data
    const QMetaObject *superdata;
    const char *stringdata;
    const uint *data;
    const void *extradata;
  } d;
};

QMetaObject中有一個嵌套結(jié)構(gòu)封裝了所有的數(shù)據(jù):
const QMetaObject superdata;//元數(shù)據(jù)代表的類的基類的元數(shù)據(jù)
const char
stringdata;//元數(shù)據(jù)的簽名標記
const uint *data;//元數(shù)據(jù)的索引數(shù)組的指針
const QMetaObject **extradata;//擴展元數(shù)據(jù)表的指針,指向QMetaObjectExtraData數(shù)據(jù)結(jié)構(gòu)。

struct QMetaObjectExtraData
{
#ifdef Q_NO_DATA_RELOCATION
    const QMetaObjectAccessor *objects;
#else
    const QMetaObject **objects;
#endif

    typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); //from revision 6
    //typedef int (*StaticMetaCall)(QMetaObject::Call, int, void **); //used from revison 2 until revison 5
    StaticMetacallFunction static_metacall;
};

static_metacall是一個指向Object::qt_static_metacall 的函數(shù)指針。

3、QT_TR_FUNCTIONS宏定義

宏QT_TR_FUNCTIONS是和翻譯相關(guān)的。

#define QT_TR_FUNCTIONS \
  static inline QString tr(const char *s, const char *c = 0) \
  { return staticMetaObject.tr(s, c); } \
#endif

4、Qt中其它宏的定義

Qt在/src/corelib/kernel/Qobjectdefs.h文件中定義了大量的宏。

#ifndef Q_MOC_RUN
# if defined(QT_NO_KEYWORDS)
#  define QT_NO_EMIT
# else
#   define slots
#   define signals protected
# endif
# define Q_SLOTS
# define Q_SIGNALS protected
# define Q_PRIVATE_SLOT(d, signature)
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif
#define Q_CLASSINFO(name, value)
#define Q_INTERFACES(x)
#define Q_PROPERTY(text)
#define Q_PRIVATE_PROPERTY(d, text)
#define Q_REVISION(v)
#define Q_OVERRIDE(text)
#define Q_ENUMS(x)
#define Q_FLAGS(x)
#define Q_SCRIPTABLE
#define Q_INVOKABLE
#define Q_SIGNAL
#define Q_SLOT

Qt中的大部分宏都無實際的定義,都是提供給MOC識別處理的,MOC工具通過對類中宏的解析處理生成moc_xxx.cpp文件。
在 Qt4 及之前的版本中,signals被展開成protected。Qt5則變成public,用以支持新的語法。

三、元對象編譯器MOC

1、MOC功能

A、處理Q_OBJECT宏和signals/slots關(guān)鍵字,生成信號和槽的底層代碼
B、處理Q_PROPERTY()和Q_ENUM()生成property系統(tǒng)代碼
C、處理Q_FLAGS()和Q_CLASSINFO()生成額外的類meta信息
D、不需要MOC處理的代碼可以用預定義的宏括起來,如下:

#ifndef Q_MOC_RUN
…
#endif

2、MOC限制

A、模板類不能使用信號/槽機制
B、MOC不擴展宏,所以信號和槽的定義不能使用宏, 包括connect的時候也不能用宏做信號和槽的名字以及參數(shù)
C、從多個類派生時,QObject派生類必須放在第一個。?QObject(或其子類)作為多重繼承的父類之一時,需要把它放在第一個。 如果使用多重繼承,moc在處理時假設首先繼承的類是QObject的一個子類,需要確保首先繼承的類是QObject或其子類。
D、函數(shù)指針不能作為信號或槽的參數(shù), 因為其格式比較復雜,MOC不能處理。可以用typedef把它定義成簡單的形式再使用。
E、用枚舉類型或typedef的類型做信號和槽的參數(shù)時,必須fully qualified。這個詞中文不知道怎么翻譯才合適,簡單的說就是, 如果是在類里定義的, 必須把類的路徑或者命名空間的路徑都加上, 防止出現(xiàn)混淆。如Qt::Alignment之類的,前面的Qt就是Alignment的qualifier, 必須加上,而且有幾級加幾級。
F、信號和槽不能返回引用類型
G、signals和slots關(guān)鍵字區(qū)域只能放置信號和槽的定義,不能放其它的如變量、構(gòu)造函數(shù)的定義等,友元聲明不能位于信號或者槽聲明區(qū)內(nèi)。
H、嵌套類不能含有信號和槽?
MOC無法處理嵌套類中的信號和槽,錯誤的例子:?
class A:public QObject
{
Q_OBJECT
public:
class B
{
public slots://錯誤用法

};

};
I、信號槽不能有缺省參數(shù)

3、自定義類型的注冊

Qt線程間傳遞自定義類型數(shù)據(jù)時,自己定義的類型如果直接使用信號槽來傳遞的話會產(chǎn)生下面這種錯誤:
????????? QObject::connect: Cannot queue arguments of type 'XXXXX' (Make sure 'XXXXX' is registed using qRegisterMetaType().)
???????? 原因:當一個signal被放到隊列中(queued)時,參數(shù)(arguments)也會被一起一起放到隊列中,參數(shù)在被傳送到slot之前需要被拷貝、存儲在隊列中;為了能夠在隊列中存儲參數(shù)(argument),Qt需要去construct、destruct、copy參數(shù)對象,而為了讓Qt知道怎樣去作這些事情,參數(shù)的類型需要使用qRegisterMetaType來注冊。
步驟:(以自定義XXXXX類型為例)
A、自定義類型時在類的頂部包含:#include <QMetaType>
B、在類型定義完成后,加入聲明:Q_DECLARE_METATYPE(XXXXX);
C、在main()函數(shù)中注冊自定義類類型:qRegisterMetaType<XXXXX>("XXXXX");
如果希望使用類型的引用,同樣要注冊:qRegisterMetaType<XXXXX>("XXXXX&");

4、MOC的使用

查看工程的Makefile文件可以查找到MOC生成moc_xxx.cpp文件的命令:

moc_Object.cpp: ../moc/Object.h
    /usr/local/Trolltech/Qt-4.8.6/bin/moc $(DEFINES) $(INCPATH) ../moc/Object.h -o moc_Object.cpp
因此命令行可以簡化為:
`moc Object.h -o moc_Object.cpp`

文章名稱:Qt高級——Qt元對象系統(tǒng)源碼解析
URL鏈接:http://muchs.cn/article44/gdecee.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、動態(tài)網(wǎng)站、Google、網(wǎng)站排名、企業(yè)建站網(wǎng)頁設計公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

手機網(wǎng)站建設