Rixstep
 About | ACP | Buy | Industry Watch | Learning Curve | News | Search | Test
Home » Learning Curve

Forget Message

Viva la difference: run loops in Windows and OS X.


It's amazing Windows developers put up with what they do. Following is a typical message loop in Windows.

for (;;) {
    if (!PeekMessage(&msg, hwnd, 0, 0, 0))
        UpdateToolbar();
    if (!GetMessage(&msg, 0, 0, 0))
        break;
    if (!TranslateAccelerator(hwnd, haccel, &msg) && !IsDialogMessage(hwnd, &msg)) {
        TranslateMessage(&msg); DispatchMessage(&msg);
    }
}

This is code the developer has to write into each and every project, and it's not complete as is above, and it has to be customised for each and every project.

Accelerators are keyboard shortcuts in Windowese. They have to be baked into a program file as a sort of 'resource' and then dynamically loaded within the application.

haccel = LoadAccelerators(wc.hInstance, MAKEINTRESOURCE(ACCEL));

[The wc referenced above is a 'window class' variable: the application must first - so to speak - fill out a form and 'register' the class of the application. None of this is done automatically on the part of Windows. This represents an additional ten or twenty lines of code the developer must write for each project.]

If the application has a toolbar, it must itself take on the responsibility of updating the images, enabling and disabling them, and so forth.

But the message loop is so complex that Microsoft could only find one way to do it: with PeekMessage.

PeekMessage, like GetMessage, looks into the application's message queue. As things happen to the application - as things affecting the application happen to the computer - 'messages' are generated and put in a buffer for the application to pick up. This is the essence of 'event driven programming'.

GetMessage will sit and wait for a message to come along; as long as it's waiting, the application is essentially blocked. This is what Microsoft used in the good old days to effect 'multitasking': it turned control back over to the 'operating system' which could tidy things up before replying.

PeekMessage peeks into the message queue without waiting and returns immediately. It is most often used to see if a message is waiting. The loop above keeps letting messages be processed until the queue is empty, then calls its own UpdateToolbar function to get those glyphs in working order.

GetMessage is so constructed so that if it returns zero, that means the queue is at an end and the application will exit. [More on this in a bit, because it does get very juicy.] As long as GetMessage returns greater than zero, there's a message to be processed.

But we're still not there. There was an accelerator table that was loaded - the keyboard shortcuts. We must now check to see if the message is actually a keyboard shortcut message. We have to call the built-in TranslateAccelerator.

if (!TranslateAccelerator(hwnd, haccel, &msg)...

If it was a keyboard shortcut message, TranslateAccelerator will return nonzero and we must not process the message further.

But there's still another hitch: what if our program has a dialog open?

... && !IsDialogMessage(hwnd, &msg))

We have to call the built-in IsDialogMessage and if and only if the message is not actually on its way to a dialog can we process it.

TranslateMessage(&msg); DispatchMessage(&msg);

If we want to process messages, we have to 'dispatch' them. This takes the messages back outside our address space and back into the operating system and then the operating system pushes them back into our address space again.

The dispatched messages end up at our 'window procedure'.

LRESULT APIENTRY WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

We have one window procedure to take care of all windows of the same class. Messages come through as numerical values in the message argument. Additional data comes in wParam and lParam. [The names of these arguments are historical: the former used to be a 16-bit 'word' argument, the latter was always 32-bit and 'long'; today both are 32-bit.]

Essentially the application is running code in two places at once. First there is the message queue - but if anything is to be done by the application, it is done in its window procedure. And the merry go round continues until GetMessage breaks the loop.

How break the loop? Simple. When the user opts to exit the application, several things happen. Eventually they lead to a message received by the window procedure.

switch (message) {
case WM_CLOSE:

WM_CLOSE ('window message - close') is the first of many hints that the user wants to exit. The application code - and this must be written in by the developer every time - must now call DestroyWindow.

switch (message) {
case WM_CLOSE:
    DestroyWindow(hWnd);
    break;

DestroyWindow starts a basic cleanup and also generates two new messages sent to the application, WM_DESTROY and WM_NCDESTROY. WM_DESTROY is generally sent first; WM_NCDESTROY is sent when even the 'non-client' areas of the window are going to be obliterated.

But the application must respond to one of these and must respond with its own code and in the proper way - or else the application will never exit.

PostQuitMessage(0);

PostQuitMessage 'posts' (as opposed to 'sends' where it would wait for a reply) a WM_QUIT message to the application's message queue. GetMessage is written in such as way that when it finds WM_QUIT, it returns zero, which breaks the developer's loop and thus gives the application a chance to exit.

At this point the control flow of the application goes beyond the message loop; the initialisation function containing this loop is now allowed to continue; the initialisation code can now begin the cleanup and ultimately call ExitProcess - using a value of zero to indicate all has went well.

Ahem!

That, at any rate, is how things are 'done' in the world of Windows. Considering the complexity of just establishing basic messaging in an application, realising Microsoft leave most of the dirty work to the developer and, so to speak, never finish their own API, it becomes clearer why there are so many bugs and vulnerabilities in Windows.

The Cocoa developer sees none of the above. None of it. It's all taken care of by the operating system OS X. All the developer has to do is get the whole shebang off the ground. And this is accomplished by the following three lines of code automatically generated for every new Cocoa project.

int main(int argc, const char *argv[]) {
    return NSApplicationMain(argc, argv);
}

The last line isn't really a line of code at all: all it's got is a curly brace.

About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Copyright © Rixstep. All rights reserved.