飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

QT QApplication干了啥?

时间:2021-12-17  作者:quinlan-space  

------------恢复内容开始------------

QCoreApplicationPrivate 会取得current thread; 在windows平台创建TLS变量,记录线程信息,并将此线程记录为主线程。交由QCoreApplicationPrivate::theMainThread维护。

QThreadData *QThreadData::current(bool createIfNecessary)
{
    qt_create_tls();
    QThreadData *threadData = reinterpret_cast<QThreadData *>(TlsGetValue(qt_current_thread_data_tls_index));
    if (!threadData && createIfNecessary) {
        threadData = new QThreadData;
        // This needs to be called prior to new AdoptedThread() to
        // avoid recursion.
        TlsSetValue(qt_current_thread_data_tls_index, threadData);
        QT_TRY {
            threadData->thread = new QAdoptedThread(threadData);
        } QT_CATCH(...) {
            TlsSetValue(qt_current_thread_data_tls_index, 0);
            threadData->deref();
            threadData = 0;
            QT_RETHROW;
        }
        threadData->deref();
        threadData->isAdopted = true;
        threadData->域名eRelaxed(reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId())));

        if (!QCoreApplicationPrivate::theMainThread) {
            QCoreApplicationPrivate::theMainThread = threadData->域名Relaxed();
        } else {
            HANDLE realHandle = INVALID_HANDLE_VALUE;
            DuplicateHandle(GetCurrentProcess(),
                    GetCurrentThread(),
                    GetCurrentProcess(),
                    &realHandle,
                    0,
                    FALSE,
                    DUPLICATE_SAME_ACCESS);
            qt_watch_adopted_thread(realHandle, threadData->thread);
        }
    }
    return threadData;
}
View Code

每个private init 都会先执行上个init,首先执行QCoreApplicationPrivate::init();

initLocale(), 初始化linux相关locale,在windows平台这步好像被跳过。接着创建eventDispatcher.这是个整个qt事件机制的核心。

 plugins\platforms\域名 ,加载相关插件。先创建并初始化平台相关内容,其中创建了QWindowsGdiIntegration,记录平台相关信息。还会查找平台主题相关内容。

然后创建evenDispatcher. QWindowsGuiEventDispatcher.记录几个关键的变量:

 创建internalHwnd.什么internal_window?看上去就是一个普通的win32 窗口。

static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{
    QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
    if (!ctx->atom)
        return 0;
    HWND wnd = CreateWindow(ctx->className,    // classname
                            ctx->className,    // window name
                            0,                 // style
                            0, 0, 0, 0,        // geometry
                            HWND_MESSAGE,            // parent
                            0,                 // menu handle
                            GetModuleHandle(0),     // application
                            0);                // windows creation data.

    if (!wnd) {
        qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");
        return 0;
    }

#ifdef GWLP_USERDATA
    SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher));
#else
    SetWindowLong(wnd, GWL_USERDATA, reinterpret_cast<LONG>(eventDispatcher));
#endif

    return wnd;
}
qt_create_internal_window

QWindowsMessageWindowClassContext又干了什么?保存了一些WNDCLASS信息,className 是个奇怪的值,后面的数值是函数地址,QEventDispatcherWin32_Internal_Widget140726494787934。qt_internal_proc这个回调也重要。HWND_MESSAGE表明这是一个专门用于回调的窗口,这个窗口并不会显示出来,不会接受广播消息,自然不会接收到鼠标或键盘消息,只接受指定给它的消息。

QWindowsMessageWindowClassContext

将创建的disPatcher 作为用户数据保存在窗口的缓存。对当前线程设置钩子。

qt_GetMessageHook:指向相应的挂钩处理过程.若参数dwThreadId为0或者指示了一个其他进程创建的线程之标识符,则参数lpfn必须指向一个动态链接中的挂钩处理过程.否则,参数lpfn可以指向一个与当前进程相关的代码中定义的挂钩处理过程.

hMod(NULL):指示了一个动态链接的句柄,该动态连接库包含了参数lpfn 所指向的挂钩处理过程.若参数dwThreadId指示的线程由当前进程创建,并且相应的挂钩处理过程定义于当前进程相关的代码中,则参数hMod必须被设置为NULL(0).

WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。

至此,eventdispatcher创建完毕。大致设想一下,先由钩子处理消息,再有internal_window处理消息。

eventdispatcherReady(), platform_integration->initialize();创建了一个QWindowsInputContext。接着处理命令行输入。

接着在QGuiApplicationPrivate::init()又干了啥? 在windows平台生成session_id和session_key。设置palette,初始化font。mouseDoubleClickDistance,touchDoubleTapDistance相关设置

以及生成鼠标形状信息。检查opengl相关信息。QApplicationPrivate::process_cmdline()。

void QApplicationPrivate::initialize();创建静态变量

QWidgetPrivate::mapper = new QWidgetMapper;//map WID -> WIDGET
QWidgetPrivate::allWidgets = new QWidgetSet;// set       WIDGET

下一步eventDispatcher->startingUp(); //目前是个空实现。整个application的初始化工作大致就是这些了。

QCoreApplication::exec();创建主eventLoop. eventLoop循环处理processEvents最终都会调到eventDispathcer的processEvent;

每次处理processEvents之前发送信号awake, 接着sendPostedEvents();

void QEventDispatcherWin32::sendPostedEvents()
{
    Q_D(QEventDispatcherWin32);

    if (d->sendPostedEventsTimerId != 0)
        KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
    d->sendPostedEventsTimerId = 0;

    // Allow posting WM_QT_SENDPOSTEDEVENTS message.
    d->域名eRelaxed(0);

    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->域名Relaxed());
}

sendPostedEventsTimerId是又SetTimer接口返回的(在qt_GetMessageHook里执行的),每次执行sendPostedEvents都会执行赋值0操作。会将之前postEvent,所累计的消息postEventList全部处理完毕。逐一通过sendEvent发送给对应的接受者。

sendWindowSystemEvents,处理windows本地的消息,再封装成QT自己的消息,传递给对应的接受者。

QEventDispatcherWin32::processEvents

while循环依次检查,首先处理没有ExcludeUserInputEvents的标记且queuedUserInputEvents不为空的情况。接着处理没有ExcludeSocketNotifiers且queuedSocketEvents不为空的的情况,然后处理peekMessage的信息。在处理这个分支时,peekMessage会触发前文提到的qt_GetMessageHook。我们回到qt_GetMessageHook:

 主要看这个逻辑判断,消息是否是指定发给前文的消息窗口,消息类型是否是WM_QT_SENDPOSTEDEVENTS,是否带有PM_REMOVE标记,以及d->sendPostedEventsTimerId是否为0;我们

先了解一下setTimer是干嘛的。该函数会返回一个定时器标识,给指定窗口发送一个wm_timer消息。

 源自http://域名/ivenher/articles/域名。

hook返回之后,检测ExcludeUserInputEvents是否存在,再判断是不是用户输入,会将这个事件加入到queuedUserInputEvents,直到没有ExcludeUserInputEvents再处理。对queuedSocketEvents同样处理。

if (d->internalHwnd == 域名 && 域名age == WM_QT_SENDPOSTEDEVENTS) {
                // Set result to \'true\' because the message was sent by wakeUp().
                retVal = true;
                continue;
            }

通过wakeUp来打破这个do...while循环,进行新一轮的processEvent.

当没有外部事件时,setTimer触发,不停发送timer事件。

if (域名age == WM_TIMER) 
                // Skip timer event intended for use inside foreign loop.
                if (d->internalHwnd == 域名 && 域名am == d->sendPostedEventsTimerId)
                    continue;

整个循环空转,直至接受到外部输入事件,比如主动wakeUp,或者window其他消息,当这个消息派发出去后,其实一般会调到qt_internal_proc  wm_timer处理逻辑里,至于为什么会触发到timer,难道是在捕获到系统消息后,timer事件还在继续发送,所以会调到这来?wm_timer消息会自动派发到消息窗口(hwnd_message)吗?这个函数栈很费解。

 

 可以看到在这调了一遍sendPostedEvents;

void QWindowsGuiEventDispatcher::sendPostedEvents()
{
QEventDispatcherWin32::sendPostedEvents();
QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}

void QEventDispatcherWin32::sendPostedEvents()
{
    Q_D(QEventDispatcherWin32);

    if (d->sendPostedEventsTimerId != 0)
        KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
    d->sendPostedEventsTimerId = 0;

    // Allow posting WM_QT_SENDPOSTEDEVENTS message.
    d->域名eRelaxed(0);

    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->域名Relaxed());
}

清除计时器,这也能退出do...while循环空转,d->sendPostedEventsTimerId = 0;代表新一轮的processEvent马上开始了。d->域名eRelaxed(0);代表允许给internal_window窗口 post WM_QT_SENDPOSTEDEVENTS message,其实也是setTimer的开关。timer的意义是想定时把系统消息队列的事件一次性处理干净吗?那WM_QT_SENDPOSTEDEVENTS 呢?干的好像也是差不多的事,消息队列为空,才setPostEvent,那这样的话,只会处理qt的postEvent,模态对话框起作用。差别在于是不是主动的,timer的方式是被动的,

对于系统消息 ,会通过handleWindowSystemEvent暂时加到队列,并wakeup,向internal_window窗口 post WM_QT_SENDPOSTEDEVENTS message, 下一次getMessageHook,检测到这个post给internal_window的WM_QT_SENDPOSTEDEVENTS消息时,会setTimer.

前面讲了postEvent的处理时机,那么关于sendEvent呢?注意下和sendSpontaneousEvent的区别,sendEvent是不是由系统触发的,是手动sendEvent的。

 一般的事件,先是执行本类的event函数,再在Qwidget::event 派发给具体的事件重写函数如showEvent(该函数在窗口未显示之前执行)。

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。