Qt源码学习(从Win32到Qt)

 

 

本文以一个Win32的helloworld程序开篇,

  • 程序入口WinMain

  • 注册窗口类别
  • 建立窗口,在屏幕上显示
  • 进入事件循环,不断从事件队列中取出消息来处理

而后尝试解释前述各部分分别隐藏在Qt何处:

main()

程序入口

Qt提供一个WinMain来调用main

QWidget::show()

注册窗口类别

第一次使用时会注册类别

显示窗体

和hide、setHidden都是setVisble的马甲

QApplication::exec()

进入事件循环

核心是 QEventDispatcherWin32

声明,我对Win32编程不了解,本文只是Qt学习过程中的学习笔记,本文提到内容以及用词描述可能会不太准确。

一个简单的Win32程序

 

这是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

 

考虑Qt?

 

既然是 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)

附:调用关系:

  • QWidget::create()
  • ==>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 看。

  • QCoreApplication::filterEvent()
  • QWidget::winEvent()

接下来以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;

 

通过这个,我们看到:

  • 消息函数接受到的消息被封装成相应的QEvent,然后发送到Qt自身的事件循环中。
  • 我们可以看到接收事件的对象是如何一步一步被确定的。对于wheel事件:
    • 首先是光标下的widget(注意popup widget的处理)
    • 如果该widget不接受,则发送到有焦点的widget。
  • 通过这儿,我们应该容易理解QWheel中这句话的真实含义了:

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

  • 注意用户输入事件和socket通知事件的处理(放入queued队列)
  • 注意此处也有一个 filterEvent,和QApplication提供的过滤器比较一下。发现谁更厉害没?

写到到这个地方,似乎从win32到Qt的对比分析已经做完了。恩,我也觉得差不多,只是...

对与 QEventDispatcherWin32 这个东西,我们还有很多话没说。它是事件循环的关键,而且它不止在主线程使用(我们肯定都知道QThread::exec())。

QEventDispatcherWin32补遗

 

在QTimer源码分析(以Windows下实现为例) 我们提到了它和定时器Timer的密切关系,刚刚又提到它是事件循环的关键,还有一点似乎还需要提一下:

  • QApplication 初始化时创建该对象

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 也在该文件内(略过)

  • 在创建内部窗口的同时,它还安装了一个钩子(Hook)

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 写过这方面的东西了(链接附文后),不过我还是认为自己的更详细一点。

参考

  • Programming Windows
  • http://blog.csdn.net/tingsking18/archive/2009/10/28/4737925.aspx

 

你可能感兴趣的:(windows,null,processing,qt,callback,events)