QT QApplication干了啥?
------------恢复内容开始------------
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::processEventswhile循环依次检查,首先处理没有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(该函数在窗口未显示之前执行)。