1. 程式人生 > >詳細分析Qt中moc檔案

詳細分析Qt中moc檔案

詳細分析Qt中moc檔案
  一直想寫一片詳細分析Qt中moc檔案的文章,今天終於是完成了。迫不及待的分享給大家,希望大家可以賞個臉,認真的看完,希望對大家的學習也有幫助。請看下面的分析Qt中moc檔案的詳細內容。
  Qt 不是使用“標準的”C++語言編寫,而是對其進行了一定程度的擴充套件。我們可以從Qt增加的關鍵字看出來:signals、slots或emit。但是使用gcc編譯時,編譯器並不認識這些非標準c++的關鍵字,那麼就需要Qt自己將擴充套件的關鍵字處理成標準的C++程式碼。Qt在編譯之前會分析原始檔,當發現包含了 Q_OBJECT 巨集,則會生成另外一個標準的C++原始檔,這個原始檔中包含了 Q_OBJECT 巨集的實現程式碼,這個原始檔名字是將原檔名前面加上 moc_ 構成,這個新的檔案同樣將進入編譯系統,最終被連結到二進位制程式碼中去,此時,Qt將自己增加的擴充套件轉換成了標準的C++檔案,moc 全稱是 Meta-Object Compiler,也就是“元物件編譯器”。這就是moc檔案的由來。
  下面我們來分析一下Moc檔案:
  一 示例程式碼如下:
  #include
  class CTestMoc : public QObject
  {
  Q_OBJECT
  public:
  CTestMoc(){}
  ~CTestMoc(){}
  signals:
  void Test1();
  void Test2(int iTemp);
  private slots:
  void OnTest1();
  void OnTest2(int iTemp);
  };
  二 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:
  Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
  struct QPrivateSignal {};
  此巨集在QObjectdefs.h標頭檔案中定義
  1 Q_OBJECT_CHECK 定義如下:
  #define Q_OBJECT_CHECK
  template inline void qt_check_for_QOBJECT_macro(const ThisObject &_q_argument) const
  { int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i + 1; }
  巨集展開最終會呼叫qYouForgotTheQ_OBJECT_Macro這個行內函數。這個函式始終返回0,但是很不明白,為什麼之後還要新增一句 i=i?,刨根之後,發現Q_OBJECT_CHECK巨集並沒有做什麼工作。
  inline int qYouForgotTheQ_OBJECT_Macro(T, T) { return 0; }
  2 static const QMetaObject staticMetaObject 靜態的元物件,這個物件在moc檔案裡會構建,在那裡就能看到整個訊號&槽的全貌。
  3 virtual const QMetaObject *metaObject() const; 返回一個元物件。
  4 virtual void *qt_metacast(const char *); 元物件中的字元資料轉換。
  5 virtual int qt_metacall(QMetaObject::Call, int, void **); 元物件呼叫入口,注意此函式是public的,槽函式呼叫也是由這個函式開始。
  6 static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); 由qt_metacall函式呼叫,槽函式呼叫真正處理函式。Q_DECL_HIDDEN_STATIC_METACALL這個巨集看到最後和linux系統有關,其它系統這個巨集是一個空的巨集。
  三 Moc檔案分析
  先說結論在這裡。
  1 Qt的訊號&槽之間的呼叫不是通過指標方式呼叫的而是通過索引方式來呼叫的.
  2 訊號也是一個函式。
  Moc檔案有幾個重要資料結構,把這幾個結構之間關係講清楚大家就清楚Qt的訊號槽機制是如何工作的了。
  第一個結構是 qt_meta_stringdata_CTestMoc_t 定義如下:
  struct qt_meta_stringdata_CTestMoc_t {
  QByteArrayData data[7];
  char stringdata[45];
  };
  data欄位是一個由byte陣列組成的陣列,陣列大小根據訊號&槽個數有關,這個陣列在呼叫QObject的connect函式時用來匹配訊號名或槽名。
  stringdata 存放的是字元資源,存放全部的訊號名、槽名、類名。
  static const qt_meta_stringdata_CTestMoc_t qt_meta_stringdata_CTestMoc = {
  {
  QT_MOC_LITERAL(0, 0, 8),
  QT_MOC_LITERAL(1, 9, 5),
  QT_MOC_LITERAL(2, 15, 0),
  QT_MOC_LITERAL(3, 16, 5),
  QT_MOC_LITERAL(4, 22, 5),
  QT_MOC_LITERAL(5, 28, 7),
  QT_MOC_LITERAL(6, 36, 7)
  },
  “CTestMoc\0Test1\0\0Test2\0iTemp\0OnTest1\0”
  “OnTest2\0”
  };
  qt_meta_stringdata_CTestMoc 這個就是一個 qt_meta_stringdata_CTestMoc_t結構體的例項。
  QT_MOC_LITERAL(0, 0, 8), 這個巨集生成一個byte陣列,第一引數是索引,大家可以看到索引是由 0 - 6 共7個組成,對應的是data欄位的長度7,第二個引數是在stringdata欄位中的開始位置,第三個引數是長度。
  例如 QT_MOC_LITERAL(0, 0, 8) 索引是0, 開始位置是0, 長度是8,對應的字元是"CTestMoc",後面的以此類推。
  第二個結構是 static const uint qt_meta_data_CTestMoc[]
  這個結構體描述的是訊號&槽在呼叫時的索引、引數、返回值等資訊。
  static const uint qt_meta_data_CTestMoc[] = {
  // content:
  7, // revision
  0, // classname
  0, 0, // classinfo
  4, 14, // methods
  0, 0, // properties
  0, 0, // enums/sets
  0, 0, // constructors
  0, // flags
  2, // signalCount
  // signals: name, argc, parameters, tag, flags
  1, 0, 34, 2, 0x06,
  3, 1, 35, 2, 0x06,
  // slots: name, argc, parameters, tag, flags
  5, 0, 38, 2, 0x08,
  6, 1, 39, 2, 0x08,
  // signals: parameters
  QMetaType::Void,
  QMetaType::Void, QMetaType::Int, 4,
  // slots: parameters
  QMetaType::Void,
  QMetaType::Void, QMetaType::Int, 4,
  0 // eod
  };
  這個陣列的前14個uint 描述的是元物件的私有資訊,定義在qmetaobject_p.h檔案的QMetaObjectPrivate結構體當中,QMetaObjectPrivate結構體我們不做深入分析,但是,在這個結構體中4, 14, // methods這個資訊描述的是訊號&槽的個數和在表中的偏移量,即14個uint之後是資訊&槽的資訊
  qt_meta_data_CTestMoc這個表中我們可以看到每描述一個訊號或槽需要5個uint
  例如,從表的第14個uint開始描述的訊號資訊
  // signals: name, argc, parameters, tag, flags
  1, 0, 34, 2, 0x06,
  3, 1, 35, 2, 0x06,
  name:對應的是qt_meta_stringdata_CTestMoc 索引,例如1 對應的是Test1
  argc:引數個數
  parameters : 引數的在qt_meta_data_CTestMoc這個表中的索引位置。
  例如 // signals: parameters
  QMetaType::Void,
  QMetaType::Void, QMetaType::Int, 4,
  void 是訊號的返回值,QMetaType::Int是引數型別, 4 是引數名,在qt_meta_stringdata_CTestMoc中的索引值。
  tag:這個欄位的數值對應的是qt_meta_stringdata_CTestMoc 索引,在這個moc檔案裡對應的是一個空字串,具體怎麼用,在原始碼裡沒看懂。
  flags:是一個特徵值,是在 enum MethodFlags 列舉中定義。
  enum MethodFlags {
  AccessPrivate = 0x00,
  AccessProtected = 0x01,
  AccessPublic = 0x02,
  AccessMask = 0x03, //mask
  MethodMethod = 0x00,
  MethodSignal = 0x04,
  MethodSlot = 0x08,
  MethodConstructor = 0x0c,
  MethodTypeMask = 0x0c,
  MethodCompatibility = 0x10,
  MethodCloned = 0x20,
  MethodScriptable = 0x40,
  MethodRevisioned = 0x80
  };
  大家可以看到,槽對應的是MethodSlot 0x08, 訊號對應的是MethodSignal 和AccessPublic 相或。
  第三部分 QObject 中靜態函式 qt_static_metacall 實現
  void CTestMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
  {
  if (_c == QMetaObject::InvokeMetaMethod) {
  CTestMoc *_t = static_cast(_o);
  switch (_id) {
  case 0: _t->Test1(); break;
  case 1: _t->Test2((reinterpret_cast< int(

)>(_a[1]))); break;
  case 2: _t->OnTest1(); break;
  case 3: _t->OnTest2((reinterpret_cast< int()>(_a[1]))); break;
  default: ;
  }
  …
  }
  現在看這個就比較直觀了,qt_metacall方法通過索引呼叫其它內部方法。Qt動態機制不採用指標,而由索引實現。實際呼叫方法的工作由編譯器實現。這使得訊號和槽的機制執行效率比較高。
  引數由一個指向指標陣列的指標進行傳遞,並在呼叫方法時進行適當的轉換。當然,使用指標是將不同型別的引數放在一個數組的唯一辦法。引數索引從1開始,因為0號代表函式返回值。
  第四部分 QObject 中靜態staticMetaObject的賦值
  const QMetaObject CTestMoc::staticMetaObject = {
  { &QObject::staticMetaObject, qt_meta_stringdata_CTestMoc.data,
  qt_meta_data_CTestMoc, qt_static_metacall, 0, 0}
  };
  ok,通過這個靜態變數就儲存了moc檔案的訊號&槽的呼叫索引資訊。在訊號&槽繫結的時候就是通過這些資訊一步一步建立的繫結關係。
  第四部分 訊號就是函式。
  大家可以Moc檔案中看到訊號的實現。
  以上就是詳細分析Qt中moc檔案的內容,不知道大家是否堅持看完了,相信看完後對你很有幫助。更多參考資料可以在華清遠見的官網進行檢視,華清遠見提供免費的教程,供大家學習!