QT源碼分析(從QApplication開始)


QT源碼分析

轉載自:http://no001.blog.51cto.com/1142339/282130

 

今天,在給同學講東西的時候,談到了Qt源代碼的問題,才發現自己對Qt機制的了解是在太少了,而Qt的魅力也在於它的開源。因此,決定,從今天起,每天堅持進行1小時以上的源碼分析,無論如何,不能間斷。
看到那無數的工程,從什么地方開始呢?想想看,也就是從自己寫的程序的運行機制作為入口點吧,希望可以窺探到一些Qt的架構知識。
所有的Qt GUI程序都是從QApplication開始的,那么我們就從QApplication的構造函數開始吧。
最初的一個基於MainWindow的GUI應用程序是這樣的:
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
從頭文件#include <QtGui/QApplication>可以看出來,程序時從QtGui工程中開始的,讓我們來一看究竟嘍。
找到了QApplication的真實路徑:
gui/kernel/qapplication.h
這里是頭文件:
#include <QtCore/qcoreapplication.h>
#include <QtGui/qwindowdefs.h>
#include <QtCore/qpoint.h>
#include <QtCore/qsize.h>
#include <QtGui/ qcursor.h>
可以看出來,該類使用了來自QtCore中的一些程序。QPoint,QSize這些數據結構,以及QCoreApplication(這個會有些什么內容呢,比較好奇)。
這里猜測qwindowdefs.h文件應該是用於存放全局定義的,qcursor.h這個比較明顯,就是光標。
后面還有一些比較奇怪的宏定義:
QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
這兩個宏的定義是空的,不知道有什么用,有待以后考究,暫時認為是為了做標識吧。
QT_MODULE(Gui)
這個會是什么意思呢?等待以后研究了……
下面是一些前向聲明:
class QSessionManager;
class QDesktopWidget;
class QStyle;
class QEventLoop;
class QIcon;
class QInputContext;
template <typename T> class QList;
class QLocale;
#if defined(Q_WS_QWS)
class QDecoration;
#endif
class QApplication;
class QApplicationPrivate;
模板類的前向聲明還是頭一次見到:template <typename T> class QList;現在不會用……以后研究,看樣子Qt的源碼真的非常復雜哦。
看下接下來的部分:
#if defined(qApp)
#undef qApp
#endif
#define qApp (static_cast<QApplication *>(QCoreApplication::instance()))
這里將qApp宏定義為一個QApplication類型的指針。在此猜測,QCoreApplication的設計采用了單例設計模式。
終於看到類定義了:
class Q_GUI_EXPORT QApplication : public QCoreApplication
原來QApplication是QCoreApplication的子類哦,怪不得要做類型轉換,但是這樣的轉換安全嗎?有待考證。
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:
 
對於這些代碼的詳細分析,以后進行。代碼才qobjectdefs.h中。
下面是一些屬性的定義,也是利用了元對象系統:
Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection)
Q_PROPERTY(QIcon windowIcon READ windowIcon WRITE setWindowIcon)
Q_PROPERTY(int cursorFlashTime READ cursorFlashTime WRITE setCursorFlashTime)
Q_PROPERTY(int doubleClickInterval READ doubleClickInterval WRITE setDoubleClickInterval)
Q_PROPERTY(int keyboardInputInterval READ keyboardInputInterval WRITE setKeyboardInputInterval)
#ifndef QT_NO_WHEELEVENT
Q_PROPERTY(int wheelScrollLines READ wheelScrollLines WRITE setWheelScrollLines)
#endif
Q_PROPERTY(QSize globalStrut READ globalStrut WRITE setGlobalStrut)
Q_PROPERTY(int startDragTime READ startDragTime WRITE setStartDragTime)
Q_PROPERTY(int startDragDistance READ startDragDistance WRITE setStartDragDistance)
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed)
#ifndef QT_NO_STYLE_STYLESHEET
Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet)
詳細的分析,以后進行,我們今天得主要目的是探究構造函數是如何運行的。
看到了如下的枚舉類型,不知道有何用意,以后詳細研究。
public:
enum Type { Tty, GuiClient, GuiServer };
終於 看到構造函數了:
QApplication(int &argc, char **argv, int = QT_VERSION);
QApplication(int &argc, char **argv, bool GUIenabled, int = QT_VERSION);
QApplication(int &argc, char **argv, Type, int = QT_VERSION);
通常情況下,都忽略了還有版本信息這樣一個參數,會有什么用呢?……
先不去看下面的類定義了,需要什么再看,要不然,光類定義都搞不定了。
現在深入到構造函數當中看個究竟:
首先是文檔內容:
Initializes the window system and constructs an application object with
\a argc command line arguments in \a argv.
\warning The data referred to by \a argc and \a argv must stay valid for
the entire lifetime of the QApplication object. In addition, \a argc must
be greater than zero and \a argv must contain at least one valid character
string.
警告中提到了傳遞參數的生存期問題,由此可以知道,Qt並不負責保存命令行參數的數據,而是簡單的保留了對象的指針。
The global \c qApp pointer refers to this application object. Only one
application object should be created.
看來之前的猜測沒有錯誤,Qt在QCoreApplication的創建上采用了單例模式。
This application object must be constructed before any \l{QPaintDevice}
{paint devices} (including widgets, pixmaps, bitmaps etc.).
現在只能先注意這個問題,等以后探究其原因。
\note \a argc and \a argv might be changed as Qt removes command line
arguments that it recognizes.
再下面的文檔是Qt的Debug選項
Qt debugging options (not available if Qt was compiled without the QT_DEBUG
flag defined):
\list
\o -nograb, tells Qt that it must never grab the mouse or the
keyboard.
\o -dograb (only under X11), running under a debugger can cause an
implicit -nograb, use -dograb to override.
\o -sync (only under X11), switches to synchronous mode for
debugging.
\endlist
See \l{Debugging Techniques} for a more detailed explanation.
在文檔中查找Debugging Techniques會有很詳細的解釋。
QApplication::QApplication(int &argc, char **argv)
: QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient))
{ Q_D(QApplication); d->construct(); }
QApplication::QApplication(int &argc, char **argv, int _internal)
: QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient))
{ Q_D(QApplication); d->construct(); QApplicationPrivate::app_compile_version = _internal;}
終於看到構造函數了,不過時間都已經過去一個多小時……可以好好研究下了。
QApplication::QApplication(int &argc, char **argv)
: QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient))
{ Q_D(QApplication); d->construct(); }
不能理解的是,這個構造函數能被調用到嗎?
QApplication(int &argc, char **argv, int = QT_VERSION);
QApplication(int &argc, char **argv, bool GUIenabled, int = QT_VERSION);
QApplication(int &argc, char **argv, Type, int = QT_VERSION);
聲明是上面的樣子。去測試一下。
原來Qt還有其他的一些構造函數:
#if defined(Q_INTERNAL_QAPP_SRC) || defined(qdoc)
QApplication(int &argc, char **argv);
QApplication(int &argc, char **argv, bool GUIenabled);
QApplication(int &argc, char **argv, Type);
#if defined(Q_WS_X11)
QApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0);
QApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
#endif
#endif
經過追蹤之后,發現程序的構造順序是這樣的:
QObjectData->QObjectPrivate->QCoreApplicationPrivate->QApplicationPrivate->QObjectPrivate->QObject->QCoreApplication->QApplication
首先,我們從最開始的QObjectData類進行研究:
class QObjectData {
public:
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
QObjectList children;
uint isWidget : 1;
uint pendTimer : 1;
uint blockSig : 1;
uint wasDeleted : 1;
uint ownObjectName : 1;
uint sendChildEvents : 1;
uint receiveChildEvents : 1;
uint inEventHandler : 1;
uint inThreadChangeEvent : 1;
uint unused : 23;
int postedEvents;
};
以上是整個類的實現,我們發現,該類只有數據成員,也就是一個純的數據封裝。虛的析構函數說明,該類將被其他類所繼承。同時,通過資料,我了解到,Qt在此的設計模式采用了句柄實體模式,也就是以QObject為基類的類一般都是句柄類,一般只有一個指針指向一個實體類,在實體類中保存全部的數據。這樣做,第一是將數據與實現分離,方便了以后修改,同時使得函數傳遞對象的速度變得很快,而不需要傳遞不安全的指針。
另外一個問題是我看到了一個以前一直沒見過的語法現象:uint isWidget : 1;經過查資料,發現,該語法現象稱作位域,位域的產生是為了節省空間,是一個C語言的語法規則。在變量定義后的數字表示了該變量只會使用1字節,編譯器可以對其存儲結構進行優化。
QObject *q_ptr;
QObject *parent;
QObjectList children;
      后面兩個應該是分別保存了父類指針,和子類對象指針,形成一個樹形的結構。
     由於該類沒有構造函數,因此,該類就分析到這里。接下來看其子類的構造方法。
QObjectPrivate
     該類在qobject_p.h中,構造方法比較簡單,初始化了一些屬性
QObjectPrivate::QObjectPrivate(int version)
: threadData(0), currentSender(0), currentChildBeingDeleted(0), connectionLists(0)
{
if (version != QObjectPrivateVersion)
qFatal("Cannot  mix incompatible Qt libraries");
// QObjectData initialization
q_ptr = 0;
parent = 0; // no parent yet. It is set by setParent()
isWidget = false; // assume not a widget object
pendTimer = false; // no timers yet
blockSig = false; // not blocking signals
wasDeleted = false; // double-delete catcher
sendChildEvents = true; // if we should send ChildInsert and ChildRemove events to parent
receiveChildEvents = true;
postedEvents = 0;
extraData = 0;
connectedSignals = 0;
inEventHandler = false;
inThreadChangeEvent = false;
deleteWatch = 0;
}
在整個類的傳遞過程中,我們一直可以看到一個Qt版本的宏定義被提供,在這里可以看到,當版本不一致時,會導致嚴重的警告,並且運行會失敗。
同時我們也看到了,Qt在對這些屬性賦值的時候,確實只用到了0、1兩個數值。
         就這樣,這個類的構造函數看完了,下面是QCoreApplicationPrivate類了。該類構造函數,將系統傳遞的命令行參數接收了。
QCoreApplicationPrivate(int &aargc, char **aargv)
可以看到,在類的定義中
int &argc;
char **argv;
我們看到了命令行參數的引用與指針,也就是說,Qt是不負責維護命令行參數的數據的。這也是昨天為什么會看到其文檔中會提到保證命令行數據始終有效。
static const char *const empty = "";
if (argc == 0 || argv == 0) {
argc = 0;
argv = (char **)&empty; // ouch! careful with QCoreApplication::argv()!
}
這里,Qt在命令行參數為空時做了賦值,同時將argv的指針指向了一個空字符串。這樣是為了安全嗎?不是很理解……
#ifdef Q_OS_UNIX
qt_application_thread_id = QThread::currentThreadId();
#endif
在這里,我們看到了,如果是在UNIX系統中時,將保存當前線程ID,具體有什么用意呢?或許以后會知道的……
看到一句提示:
// note: this call to QThread::currentThread() may end up setting theMainThread!
具體含義,可能是說,該處調用currentThread可能會導致主線程終止。真正含義及原因,有待考證。
if (QThread::currentThread() != theMainThread)
qWarning("WARNING: QApplication was not created in the main() thread.");
這里判斷界面應用程序是否在主線程中創建,Qt目前是不支持在其他線程中進行界面類操作的。
今天的起點是QObject的構造函數。
Q_INVOKABLE explicit QObject(QObject *parent=0);
構造函數的聲明是這樣的,首先看到的是宏定義:Q_INVOKABLE,該宏在Qt文檔中有說明,用於將函數在元對象系統中注冊。Explicit關鍵字,該關鍵字可以阻止不應該允許的經過轉換構造函數進行的隱式轉換的發生。聲明為explicit的構造函數不能在隱式轉換中使用。
接下來是實現的函數頭:
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
可以看出來,該類創建了QObjectPrivate的對象,這樣使得經常修改的內部操作與標准接口分離,同時還為QObject類做了減肥。
Q_D(QObject);
qt_addObject(d_ptr->q_ptr = this);
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
parent = 0;
setParent(parent);
首先看到的是Q_D宏,該宏的實現是這樣的:
#define Q_D(Class) Class##Private * const d = d_func()
傳入參數是QObject,將宏展開后得到:
QObjectPrivate * const d = d_func();
這里的d_func()有幾種不同的實現:
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
friend class Class##Private;
#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(Dptr); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(Dptr); } \
friend class Class##Private;
暫時還不清楚到底是哪個,繼續往下看了。
qt_addObject( d_ptr->q_ptr = this);
這個函數中,首先是d_ptr->q_ptr = this,該方法將當前對象的指針賦值給了剛剛創建的QObjectPrivate類中的指針。接下來看這個函數:
qt_addObject()
沒有找到具體的函數實現,只是發現了采用C函數的聲明。
extern "C" Q_CORE_EXPORT void qt_addObject(QObject *)
接下來的做法是獲取線程ID了
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
parent = 0;
詳細實現在分析QThreadData時再做了解。
setParent(parent);
這句話設置父對象的指針,為了方便引用。開始感覺到,Qt的實現機制中,對靈活性的追求要勝於對速度的追求。
接下來的構造函數是QCoreApplication類的了,終於要接觸到最后兩個構造函數了。
簡單看下文檔說明,QCoreApplication類提供了一個事件循環,用於提供對控制台Qt應用程序的支持。
這個類采用了無GUI的事件循環,所有的操作系統事件都將進入到主循環當中。負責對其他來源的處理,並派遣。同時該類負責了對象的初始化以及結束。程序的時間循環在調用exec()函數時開始。長時間的操作可以通過調用processEvents()來保證應用程序的響應。
做了下測試,在一個死循環中調用了該函數,發現程序又可以正常響應了。
得到另外一個說明是:exit()函數需要在所有事件循環退出后才會返回。
言歸正傳,現在看其構造函數,發現構造了對象QObject(p, 0),之后調用了init函數。看到一個標記,說子類需要調用QCoreApplicationPrivate::eventDispatcher->startingUp();
函數。現在還不清楚是什么含義。
跟蹤到init()函數,發現這里做的事情還真不少。
Q_D(QCoreApplication);
首先創建了QCoreApplicationPrivate的一個指針。下面是針對不同平台的一些定義。
在Windows平台下,調用了set_winapp_name()函數。又是一個外部定義的函數,暫時找不到實現,所以不能繼續了。但是看到一個提示,當qWinMain()函數無效的時候,用於獲取應用程序名稱和實例。
再往下是Q_ASSERT_X宏定義,該宏定義用於打印斷言信息。
ASSERT failure in divide: "division by zero", file mainwindow.cpp, line 19。
 
http://blog.csdn.net/wsh6759/article/details/7431869


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM