【Qt源码笔记】从WinMain说起
Qt在各个平台下都是对平台API进行了一些包装。Windows下是对Win32API的封装。如果是Windows平台的GUI Application就一定是从WinMain
开始。
不难发现WinMain
就在qtmain_win.cpp中。
extern "C" int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR /*cmdParamarg*/, int /* cmdShow */)
{
int argc;
wchar_t **argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
if (!argvW)
return -1;
char **argv = new char *[argc + 1];
for (int i = 0; i < argc; ++i)
argv[i] = wideToMulti(CP_ACP, argvW[i]);
argv[argc] = Q_NULLPTR;
LocalFree(argvW);
const int exitCode = main(argc, argv);
for (int i = 0; i < argc && argv[i]; ++i)
delete [] argv[i];
delete [] argv;
return exitCode;
}
在这里的WinMain
仅仅充当一个入口,所有对命令行参数 这些 都交由main
来处理。而这个main
就是我们在自己的主程序中写的main
。
入口找到以后,在Windows下的程序还有一个很重要的东西,那就是消息循环。Win32中经典的PeekMessage()
、DispatchMessage()
和TranslateMessage()
。这些东西在程序中注册的回调函数中被调用,用来处理和解析消息。Qt本身也要依赖这些,只不过在上边进行了一些封装。调到我们自己的程序里看到的就是winEvent()
或者是一些QEvent
了。
我们写Qt程序的时候,一个很常见的套路是:
int main(int argv, char **args)
{
QApplication app(argv, args);
//todo...
return app.exec();
}
这个回调函数就是在app.exec()
中被注册(准确的说回调函数是由在这个方法中调用的其他方法注册)。不难找到一个叫做qeventdispatcher_win.cpp文件,名字已经很明确了,就是处理Qt事件的。我们会找到一个类QEventDispatcherWin32
。可以发现一个processEvents()
方法。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd) {
createInternalHwnd();
wakeUp(); // trigger a call to sendPostedEvents()
}
//...
do {
DWORD waitRet = 0;
HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt) {
DWORD nCount = d->winEventNotifierList.count();
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// process queued user input events
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// process queued socket events
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage) {
if ((flags & QEventLoop::ExcludeUserInputEvents)
&& ((msg.message >= WM_KEYFIRST
&& msg.message <= WM_KEYLAST)
|| (msg.message >= WM_MOUSEFIRST
&& msg.message <= WM_MOUSELAST)
|| msg.message == WM_MOUSEWHEEL
|| msg.message == WM_MOUSEHWHEEL
|| msg.message == WM_TOUCH
#ifndef QT_NO_GESTURES
|| msg.message == WM_GESTURE
|| msg.message == WM_GESTURENOTIFY
#endif
|| msg.message == WM_CLOSE)) {
// queue user input events for later processing
d->queuedUserInputEvents.append(msg);
continue;
}
//...
}
}
//...
if (haveMessage) {
//...
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//...
retVal = true;
}
// still nothing - wait for message or signalled objects
canWait = (!retVal
&& !d->interrupt
&& (flags & QEventLoop::WaitForMoreEvents));
if (canWait) {
DWORD nCount = d->winEventNotifierList.count();
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
for (int i=0; i<(int)nCount; i++)
pHandles[i] = d->winEventNotifierList.at(i)->handle();
emit aboutToBlock();
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
emit awake();
if (waitRet - WAIT_OBJECT_0 < nCount) {
d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
retVal = true;
}
}
} while (canWait);
// ...
return retVal;
}
代码比较长,省略了一些暂时不关注的,在这里我们可以看到我们最熟悉的Win32的消息枚举和方法。现在问题来了DispatchMessage()
以后,程序的调用会走到我们注册的回调函数,由我们自己来处理消息。所以要找到这个回调。
Qt的这个回调函数是qt_internal_proc()
。那下一个问题就是在哪里注册的这个回调函数。 这个可以回顾一下Win32程序的一般套路:
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
//Step 1: Registering the Window Class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
如果要写一个Win32的程序,都要先注册一个Windows Class
,就是在lpfnWndProc
中指明我们的回调方法。再回过头去看processEvents()
方法中createInternalHwnd()
的调用:
void QEventDispatcherWin32::createInternalHwnd()
{
Q_D(QEventDispatcherWin32);
if (d->internalHwnd)
return;
d->internalHwnd = qt_create_internal_window(this);
installMessageHook();
// start all normal timers
for (int i = 0; i < d->timerVec.count(); ++i)
d->registerTimer(d->timerVec.at(i));
}
可以看到一个名叫qt_create_internal_window()
的方法,顾名思义。在此方法中会获取一个QWindowsMessageWindowClassContext
,看一下他的构造函数,一目了然:
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext()
: atom(0), className(0)
{
// make sure that multiple Qt's can coexist in the same process
const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")
+ QString::number(quintptr(qt_internal_proc));
className = new wchar_t[qClassName.size() + 1];
qClassName.toWCharArray(className);
className[qClassName.size()] = 0;
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 = className;
atom = RegisterClass(&wc);
if (!atom) {
qErrnoWarning("%s RegisterClass() failed", qPrintable(qClassName));
delete [] className;
className = 0;
}
}
不难发现回调函数qt_internal_proc()
就是在这里注册的。
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
if (message == WM_NCCREATE)
return true;
MSG msg;
msg.hwnd = hwnd;
msg.message = message;
msg.wParam = wp;
msg.lParam = lp;
QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance();
long result;
if (!dispatcher) {
if (message == WM_TIMER)
KillTimer(hwnd, wp);
return 0;
} else if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result)) {
return result;
}
//...
return result;
}
Qt就是这样将Win32的调用包装成了自己的调用。