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

QT源码分析(从QApplication开始)的更多相关文章

  1. Qt源码分析之QObject

    原文:http://blog.csdn.net/oowgsoo/article/details/1529284 我感觉oowgsoo兄弟写的分析相当透彻,赞! 1.试验代码: #include < ...

  2. Qt事件分发机制源码分析之QApplication对象构建过程

    我们在新建一个Qt GUI项目时,main函数里会生成类似下面的代码: int main(int argc, char *argv[]) { QApplication application(argc ...

  3. QT源码分析:QObject

    QT框架里面最大的特色就是在C++的基础上增加了元对象系统(Meta-Object System),而元对象系统里面最重要的内容就是信号与槽机制,这个机制是在C++语法的基础上实现的,使用了函数.函数 ...

  4. QT源码分析:QTcpServer

    最近在看有关IO复用方面的内容,自己也用标准c++库实现了select模型.iocp模型.poll模型.回过头来很想了解QT的socket是基于什么模型来实现的,所以看了QT关于TcpServer实现 ...

  5. Qt源码分析之信号和槽机制

    Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个 ...

  6. Qt源码分析之信号和槽机制(QMetaObject是一个内部struct)

    Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个 ...

  7. Qt源码分析之QPointer

    QPointer是一个指针封装类,其作用类似于智能指针,但是它最大的特点应该是在指针的控制上,它希望一个Qt的指针(当然是从QObject派生的)可以同时被多个类拥有,这在界面编程中当然是很常见的事情 ...

  8. QT 源码分析--1

    Ref: http://blog.sina.com.cn/s/blog_6e80f1390100qoc0.html 安装qt之后(我使用的是online自动安装),安装目录下有\5.10.1\Src\ ...

  9. Qt update刷新之源码分析总结

    大家好,我是IT文艺男,来自一线大厂的一线程序员 经过前面几次的Qt源码讲解,我相信大家对Qt update刷新机制从底层原理上有了一个深刻的理解:这次做一个收尾总结,来复盘前面几次所讲解的内容: 分 ...

随机推荐

  1. Column store index 列数据如何匹配成行数据?

    SQL Server 2012引入了列存储索引,对每列的数据进行分组和存储,然后联接所有列以完成整个索引.这不同于传统索引,传统索引对每行的数据进行分组和存储,然后联接所有行以完成整个索引. 在访问基 ...

  2. QSerialPort

    (草稿) qt5提供QSerialPort类,封装了串口的api, 可以用这个类写串口通信程序.

  3. BZOJ 1196: [HNOI2006]公路修建问题( MST )

    水题... 容易发现花费最大最小即是求 MST 将每条边拆成一级 , 二级两条 , 然后跑 MST . 跑 MST 时 , 要先加 k 条一级road , 保证满足题意 , 然后再跑普通的 MST . ...

  4. C 语言学习 之搭建环境和熟悉命令

    Open Terminal 打开终端To run a command as administrator (user "root"), use "sudo <comm ...

  5. win7系统远程连接其它计算机,并且向远程机传输文件

    首先,打开开始菜单,在程序自带的 “附件“ 中找到 "远程桌面连接"并打开,出现远程桌面对话框: 其次,在对话框左下角点击“选项”,选择“本地资源对话框”,在本地设备和资源下点击“ ...

  6. [置顶] 编程模仿boost::function和boost::bind

    boost::function和boost::bind结合使用是非常强大的,他可以将成员函数和非成员函数绑定对一个对象上,实现了类似C#的委托机制.委托在许多时候可以替代C++里面的继承,实现对象解耦 ...

  7. Oracle查看表空间使用情况

     查看表空间使用情况 select upper(f.tablespace_name) "表空间名",        d.tot_grootte_mb "表空间大小(m ...

  8. IOS学习之segmented control

    转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/27086877 作者:小马 什么是segmented control? 先上几张图: ...

  9. WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇)

    原文:WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话 ...

  10. php 中 return exit break contiue 详解

    return.break和contiue是语言结构,就如同if语句之类的,但是exit却是个函数. 1.exit函数 作用:Output a message and terminate the cur ...