本文以一个Win32的helloworld程序开篇,
程序入口WinMain
而后尝试解释前述各部分分别隐藏在Qt何处:
main() |
程序入口 |
Qt提供一个WinMain来调用main |
QWidget::show() |
注册窗口类别 |
第一次使用时会注册类别 |
显示窗体 |
和hide、setHidden都是setVisble的马甲 |
|
QApplication::exec() |
进入事件循环 |
核心是 QEventDispatcherWin32 |
声明,我对Win32编程不了解,本文只是Qt学习过程中的学习笔记,本文提到内容以及用词描述可能会不太准确。
这是Win32编程的一个hello world程序:
包含头文件,定义入口函数WinMain
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Hello"); HWND hwnd; MSG msg;
创建窗口类别(注意里面的WndProc是我们后面定义的回调函数),并注册窗口类别
WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground=(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName= szAppName; if(!RegisterClass(&wndclass)) { MessageBox( NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0; }
hwnd = CreateWindow( szAppName, // window class name TEXT("The Hello Program"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL); // creation parameters ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd);
while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
以上我们所讨论的都是必要的东西:注册窗口类别,建立窗口,在屏幕上显示窗口,进入事件循环,不断从事件队列中取出消息来处理。
实际的动作发生在消息处理函数中。该函数确定了在视窗的显示区域中显示些什么以及怎样响应用户的输入等。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch(message) { case WM_CREATE: return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); DrawText(hdc, TEXT("Hello, Windows!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
用cl或gcc编译该程序:
gcc hello.c -o hello -lgdi32 -Wl,-subsystem,windows cl hello.c user32.lib gdi32.lib
既然是 hello world,也就基本是最简单的Win32程序了。但看着还是挺复杂,难怪大家都不怎么喜欢它。
Qt(或其他的图形库/框架)多简单啊,简单几行代码一个程序就出来了。可是... 简单的表象下面呢?我们如何把一个Windows下的 Qt 程序和上面的代码对应上?
#include <QtGui/QApplication> #include <QtGui/QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel w("Hello world!"); w.show(); return app.exec(); }
入口函数 WinMain
在Qt中我们只写main函数,不写WinMain,挺有意思哈,不过在Qt Windows下链接子系统与入口函数(终结版) 中我们已经详细讨论过这个问题了(简单地说:就是qtmain.lib或libqtmain.a提供了一个WinMain,它会调用我们的main)
这个东西挺隐蔽的哈,当你对一个QWidget调用setVisble()或者它的众多马甲(比如show() )之一时,会执行这部分代码。源码位于qwidget_win.cpp 和 qapplication_win.cpp 文件中
这个循环体现在Qt中就是 QApplication::exec()。核心代码在qeventdispatcher_win.cpp 中。
当第一次调用setVisible()时,会进行窗口类别的创建,这最终会调用 qapplication_win.cpp 中的下面一个函数:
const QString qt_reg_winclass(QWidget *w) { ... WNDCLASS wc; wc.style = style; wc.lpfnWndProc = (WNDPROC)QtWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = qWinAppInst(); ... wc.lpszMenuName = 0; wc.lpszClassName = (wchar_t*)cname.utf16(); ATOM atom = RegisterClass(&wc); ...
恩,和前面的比对一下,应该是一样的吧(注意这儿注册的回调函数函数名:QtWndProc)
附:调用关系:
==>QWidgetPrivate::sys_create()
==>qt_reg_winclass()
接前面,不妨直接看看QtWndProc这个回调函数(在同一个文件内), 尽管我们都知道它里面是一个大大的switch语句,我还是贴一点它的代码出来:
// // QtWndProc() receives all messages from the main event loop // extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { MSG msg; msg.hwnd = hwnd; // create MSG structure msg.message = message; // time and pt fields ignored msg.wParam = wParam; msg.lParam = lParam; msg.pt.x = GET_X_LPARAM(lParam); msg.pt.y = GET_Y_LPARAM(lParam); // If it's a non-client-area message the coords are screen coords, otherwise they are // client coords. if (message < WM_NCMOUSEMOVE || message > WM_NCMBUTTONDBLCLK) ClientToScreen(msg.hwnd, &msg.pt); ... // send through app filter if (qApp->filterEvent(&msg, &res)) return res; ... res = 0; if (widget->winEvent(&msg, &res)) // send through widget filter RETURN(res); ... if (qt_is_translatable_mouse_event(message)) { ... }else{ switch (message) { ... case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: result = widget->translateWheelEvent(msg); break; ... } ...
注意:里面出现两处msg消息的过滤。可以对照 Manual 看。
接下来以whell事件为例,看一下Windows事件如果变成Qt中的事件,并进入Qt自身的消息循环的:
bool QETWidget::translateWheelEvent(const MSG &msg) { ... // if there is a widget under the mouse and it is not shadowed // by modality, we send the event to it first int ret = 0; QWidget* w = QApplication::widgetAt(globalPos); if (!w || !qt_try_modal(w, (MSG*)&msg, ret)) { //synaptics touchpad shows its own widget at this position //so widgetAt() will fail with that HWND, try child of this widget w = this->childAt(this->mapFromGlobal(globalPos)); if (!w) w = this; } // send the event to the widget or its ancestors { QWidget* popup = QApplication::activePopupWidget(); if (popup && w->window() != popup) popup->close(); QWheelEvent e(w->mapFromGlobal(globalPos), globalPos, delta, Qt::MouseButtons(state & Qt::MouseButtonMask), Qt::KeyboardModifier(state & Qt::KeyboardModifierMask), orient); if (QApplication::sendSpontaneousEvent(w, &e)) return true; } // send the event to the widget that has the focus or its ancestors, if different if (w != QApplication::focusWidget() && (w = QApplication::focusWidget())) { QWidget* popup = QApplication::activePopupWidget(); if (popup && w->window() != popup) popup->close(); QWheelEvent e(w->mapFromGlobal(globalPos), globalPos, delta, Qt::MouseButtons(state & Qt::MouseButtonMask), Qt::KeyboardModifier(state & Qt::KeyboardModifierMask), orient); if (QApplication::sendSpontaneousEvent(w, &e)) return true; } return false;
通过这个,我们看到:
Wheel events are sent to the widget under the mouse cursor, but if that widget does not handle the event they are sent to the focus widget.
在 QDialog 模态对话框与事件循环 与 QEventLoop 的使用两例 等blog中,已经对此做过介绍:QApplication::exec()最终将(在一个while循环内)不断调用 qeventdispatcher_win.cpp 文件中的processEvents函数:
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { ... haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE); if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents) && ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) || (msg.message >= WM_MOUSEFIRST ... || msg.message == WM_CLOSE)) { // queue user input events for later processing haveMessage = false; d->queuedUserInputEvents.append(msg); } if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers) && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) { // queue socket events for later processing haveMessage = false; d->queuedSocketEvents.append(msg); } ... if (!filterEvent(&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } ... }
这个文件很复杂,此处只摘取了个人感兴趣的片段(其实是因为其他的大段我看不太懂):
可以看到win32中熟悉的 PeekMessage、TranslateMessage、DispatchMessage
写到到这个地方,似乎从win32到Qt的对比分析已经做完了。恩,我也觉得差不多,只是...
对与 QEventDispatcherWin32 这个东西,我们还有很多话没说。它是事件循环的关键,而且它不止在主线程使用(我们肯定都知道QThread::exec())。
在QTimer源码分析(以Windows下实现为例) 我们提到了它和定时器Timer的密切关系,刚刚又提到它是事件循环的关键,还有一点似乎还需要提一下:
void QApplicationPrivate::createEventDispatcher() { Q_Q(QApplication); if (q->type() != QApplication::Tty) eventDispatcher = new QGuiEventDispatcherWin32(q); else eventDispatcher = new QEventDispatcherWin32(q); }
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher) { // make sure that multiple Qt's can coexist in the same process QString className = QLatin1String("QEventDispatcherWin32_Internal_Widget") + QString::number(quintptr(qt_internal_proc)); WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = qt_internal_proc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = qWinAppInst(); wc.hIcon = 0; wc.hCursor = 0; wc.hbrBackground = 0; wc.lpszMenuName = NULL; wc.lpszClassName = reinterpret_cast<const wchar_t *> (className.utf16()); RegisterClass(&wc); HWND wnd = CreateWindow(wc.lpszClassName, // classname wc.lpszClassName, // window name 0, // style 0, 0, 0, 0, // geometry 0, // parent 0, // menu handle qWinAppInst(), // application 0); // windows creation data. ...
其回调函数 qt_internal_proc 也在该文件内(略过)
d->getMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC) qt_GetMessageHook, NULL, GetCurrentThreadId()); if (!d->getMessageHook) { qFatal("Qt: INTERNALL ERROR: failed to install GetMessage hook"); }
在钩子的回调函数中,一些消息被PostMessage到上面提到的内部窗口中
LRESULT QT_WIN_CALLBACK qt_GetMessageHook(int code, WPARAM wp, LPARAM lp) { ... MSG *msg = (MSG *) lp; if (localSerialNumber != d->lastSerialNumber // if this message IS the one that triggers sendPostedEvents(), no need to post it again && (msg->hwnd != d->internalHwnd || msg->message != WM_QT_SENDPOSTEDEVENTS)) { PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
总算大体上理了一遍,尽管很多东西还是不懂。有错误欢迎大家指出哈 dbzhang800 2011.04.28
写的途中发现一个技术大牛 tingsking18 写过这方面的东西了(链接附文后),不过我还是认为自己的更详细一点。
http://blog.csdn.net/tingsking18/archive/2009/10/28/4737925.aspx