今天,在给同学讲东西的时候,谈到了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的真实路径:
gui/kernel/qapplication.h
这里是头文件:
#include
#include
#include
#include
#include 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 class QList;
class QLocale;
#if defined(Q_WS_QWS)
class QDecoration;
#endif
class QApplication;
class QApplicationPrivate;
模板类的前向声明还是头一次见到:template class QList;现在不会用……以后研究,看样子Qt的源码真的非常复杂哦。
看下接下来的部分:
#if defined(qApp)
#undef qApp
#endif
#define qApp (static_cast(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 **)∅ // 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(d_ptr); } \
inline const Class##Private* d_func() const { return reinterpret_cast(d_ptr); } \
friend class Class##Private;
#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
inline Class##Private* d_func() { return reinterpret_cast(Dptr); } \
inline const Class##Private* d_func() const { return reinterpret_cast(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。