Qt on Windows实现解析

= 867

就窗口系统而言,我对C/S架构的更为熟悉一些。以前看过基于X window的一些实现,比如qtopia以及其他一些嵌入式窗口系统。C/S架构的GUI系统的结构比较清晰。其中,窗口服务器负责监听输入时间、屏幕绘制以及窗口状态维护,应用程序负责接收服务器的消息并进行处理、并把状态改变反馈给窗口服务器等。

但是,Windows窗口系统使用的却是基于共享库的实现方式。共享库中保存着所用图形客户端的窗口信息,共享库和每一个客户端程序都运行在同一个进程中。基于共享库的图形系统具有响应速度快、资源开销小的优点,这也是Windows窗口系统表现非常优秀的原因。这种窗口系统带来的问题就是对共享信息访问的安全性。

在虚拟内存管理的前提下,对这些共享信息的访问无疑要借助内核态的帮助。Windows下的图形应用之所以需要提供回调函数,是因为这个函数需要在内核态下执行,以完成个性化的窗口功能。熟悉Linux信号的同学可以类比Linux信号函数与Windows窗口回调函数。虽然,Linux的信号处理函数是在用户态下执行的,但工作的逻辑却相似的,连接内核态的信息与用户态的操作。

废话说了很多,却还没有说到本文的重点。我们都知道Qt是一个跨平台的GUI库,那么问题来了Qt是如何实现跨平台的呢?Mac,Linux以及Android的窗口系统基本都是X window或者C/S派系的。从Qt的出身来看,拿下这些系统基本不在话下,那么Windows呢?作为一个对Windows窗口系统并不了解的人(《Windows内核原理与实现》没看明白),我对这个问题其实一直挺好奇的。最近终于决定看看Qt在Windows部分的源码,了却心中的一些疑惑。

1. Qt基本没有使用Windows窗口系统的原生控件

这大概是Qt实现跨平台的一大基础。以下面这个Qt程序为例,我们来探索一下Qt的控件机制。

#include <QApplication>
#include <QPushButton> 

int main(int argc, char *argv[])  
{
    QApplication app(argc, argv);  
    QPushButton hello("Hello world!"); 
    hello.resize(100, 30);   
    hello.show();
    return app.exec();
}

这个程序基本是Qt最入门级的程序了,但这个最简单的程序却有着很强的代表性,可以针对这段程序提出很多问题。第一的问题是:这个QPushButton明明就是一个按钮,怎么就成窗口了呢?这个问题看起来很矛盾,窗口和普通控件在Windows窗口系统下是有严格的区分的,而QPushButton有时是一个按钮,有时又变成了窗口。这个问题的答案很简单——Qt并没有使用Windows提供的窗口部件。

在Qt中,QWidget是所有widget部件的基类。事实上,QWidget类并不强制区分窗口和普通部件,而是可以根据情况变化。下面这个函数在QWidget初始化过程中调用到,其中的if语句包含了很多信息。当QWidget的类型为普通Widget或者子窗口,但是该QWidget不存在父窗口,那么该widget升级为一个窗口。这也是前文例子中的pushbutton可以成为一个单独窗口显示的原因。

void QWidgetPrivate::adjustFlags(Qt::WindowFlags &flags, QWidget *w)
{
    /**/
    uint type = (flags & Qt::WindowType_Mask);

    if ((type == Qt::Widget || type == Qt::SubWindow) && w && !w->parent()) {
        type = Qt::Window;
        flags |= Qt::Window;
    }
    /**/
}

2. Qt使用了单例模式来实现事件循环

依然是那个最简单的例子,大家都知道app.exec()是开始事件循环。那么,app是怎么知道这个button的呢?代码里面看不出,app和button是如何关联起来的。问题的答案就是“单例模式”,Qt中的最大的单例要数QCoreApplicaition了,这个app变量其实并不是那么重要,不信看下面的代码。

int QApplication::exec()  
{  
    //...
    return QGuiApplication::exec();  
}  
//section 3 qguiapplication.cpp  
int QGuiApplication::exec()  
{  
    //... 
#ifndef QT_NO_ACCESSIBILITY  
    QAccessible::setRootObject(qApp);  
#endif  
    //...
    return QCoreApplication::exec();  
}  

直接进入了事件循环,下面这个图就展示了Qt对Spontaneous events(鼠标、键盘以及定时器事件)的处理方式。0_1318867838Y1h7

其中Qt Event Dispatcher是一个万金油的变量,它会在各种地方被使用,包括各个窗口过程函数。应用窗口(比如前面的button窗口)给它传递Spontaneous events,隐藏窗口(实现事件循环的关键所在)也会通过Qt Event Dispatcher对事件进行传递和处理。

整个详细传递的细节还是比较复杂的(包括普通应用窗口、隐藏窗口等内容),我准备在下一篇博客中再讨论了。

发表评论