Note: After clicking on the body of this page, you can press SPACEBAR to scroll down. This is done using a very special Javascript function. Don't try this when you view other sites :)
Let us see if the above statement is 100% true.
When you write a program in a language like QuickBasic in DOS, you do not write any code that appears to be called by DOS. But when you write in Visual Basic, you write event handlers which are apparently called by Windows. In reality, the code in event handlers are not directly called by Windows. They are called by Visual Basic code. To get a good grasp of what happens behind the scenes, let us examine a minimal Windows program written in C. (I assume that you are somewhat familiar with Windows C programs. Read the SDK documentation and refer to the SDK help if you have no previous experience in this field).
#include <windows.h> // Message handling procedure LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { // Message to display in window char *pStr = "I Call You"; PAINTSTRUCT ps; HDC hdc; // Set to arbitrary values to enclose the string pStr RECT rt = {0,0,800,100}; switch (message) { // This message was created by GetMessage case WM_PAINT: hdc = BeginPaint(hwnd, &ps); // Display the string "I Call You" DrawText(hdc, pStr, strlen(pStr), &rt, DT_LEFT); EndPaint(hwnd, &ps); break; // Time to end the program case WM_DESTROY: // Send a WM_QUIT message which will be // retrieved in a subsequent GetMessage call PostQuitMessage(0); break; default: // Ask Windows to handle other messages return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char *pClass = "ICALLYOU"; WNDCLASSEX wcex; HWND hwnd; MSG msg; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; // Pass the address of the WndProc function wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = NULL; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = pClass; wcex.hIconSm = NULL; // Register the window class "ICALLYOU" RegisterClassEx(&wcex); // Create a window with class name "ICALLYOU" hwnd = CreateWindow(pClass, "I call you", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hwnd) return FALSE; // Display the window ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Main message loop // GetMessage returns 0 when a WM_QUIT message is received while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); } // msg.wParam was the value you passed to PostQuitMessage return msg.wParam; }
Now, let us examine a command line editor written in C in DOS. The program should exit when the Enter is pressed. The program checks in a loop for keys pressed and handles each key in a function, using a switch statement. Sounds familiar?
#include <conio.h> int nSpecialKey = 0; int KeyProc(int nKey) { switch(nKey) { // Special key case 0: nSpecialKey = 1; break; // Backspace key case 8: if(!nSpecialKey && wherex() > 1) { gotoxy(wherex() - 1, wherey()); putch(' '); gotoxy(wherex() - 1, wherey()); } break; // Echo only ordinary keys default: nSpecialKey ? nSpecialKey = 0 : putch(nKey); } return 0; } void main() { int nKey; clrscr(); gotoxy(1, 8); // Loop until Return while((nKey = getch()) != 13) { KeyProc(nKey); } }
The only editing key this program can handle is the Backspace key. It takes care not to display the codes of function keys, arrow keys etc.
Now compare this with the Windows program you saw above. They are very similar. Of course, the Windows program needs a lot more initialization than the DOS program. We will see why, in a short while.
Both programs have a main loop which finds out what the user has done.
// Loop until Return while((nKey = getch()) != 13) { KeyProc(nKey); } // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); }
Also, both of them have a routine that responds to what the user does. See how both of the programs have a default case for the switch statement.
Some C programmers believe that the function WndProc is always directly called by Windows. This is not true. This WndProc function normally gets called during the DispatchMessage call. (Note that there is nothing special about the name WndProc). GetMessage is similar to getch in DOS. (Of course, getch is a C library function while GetMessage is a Windows API function). In DOS, only one program will be executing at a given time. So nothing particular happens during the getch call. (Except interrupts). But GetMessage is somewhat different. It searches for messages in message queues and sometimes creates some messages like WM_PAINT and WM_TIMER. GetMessage will return to the calling application only if there is a message waiting for it. Otherwise, it will give control to another application.
DispatchMessage is actually an indirect way to let Windows call WndProc. The only parameter DispatchMessage sends to Windows is a pointer to the MSG structure filled in by the GetMessage call. One of the members of this structure is hwnd. hwnd to the main window was obtained using the following call:
hwnd = CreateWindow(pClass, "I call you", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
The first parameter passed to CreateWindow is the window class name which was registered earlier using the following call:
RegisterClassEx(&wcex);
RegisterClass passes a pointer to a WNDCLASS structure to Windows. One member of this structure is lpfnWndProc, which is set to the address of WndProc by the program. This is one of the reasons why Windows programs need extra initialization.
Windows uses the hwnd member of the MSG structure passed by DispatchMessage to find the address of its WndProc. It then calls WndProc with the other members of the MSG structure. It should be noted that DispatchMessage function will return when Windows has finished its call to WndProc. If the message loop is not present, WndProc will not be called and the window will not respond. This is true even in the case of timer messages. It is the GetMessage call that creates the timer messages and the DispatchMessage call which passes them to the WndProc function. See the figure below to understand what happens when GetMessage and DispatchMessage are called.
Why go through this monkey business of calling DispatchMessage and let Windows call WndProc? Can't we directly call WndProc? Try changing the message loop as follows and run the program.
// Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { WndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); }
You can see the program still works. But this is not recommended and you should not write programs which are coded like this.
Does this mean that you do not need all the initialization you have done? Not quite. You can see that most of the API calls in WndProc uses the hwnd as the first parameter. And in order to have an hwnd, you have to call CreateWindow and in order to call CreateWindow, you have to register a window class. (Unless you use existing window classes). Finding out the address of WndProc is not the only purpose hwnd serves.
In DOS, when you write the interrupt 1Ch (timer) handler, operating system calls your interrupt handler. You don't call the operating system to check if there was an interrupt. Your interrupt handler is executed automatically.
So why did Windows made it mandatory that all applications should create a message loop which asks Windows again and again if they have any messages? One reason is that if there is no message loop, programs will exit after showing the main window. The message loop makes sure that WinMain does not return until GetMessage retrieves a WM_QUIT message. Also DispatchMessage makes sure that control will not be switched to another program while you are in the middle of a WndProc.
How do you prove that it is DispatchMessage that sends messages to WndProc? Try changing the message loop as shown below.
while (GetMessage(&msg, NULL, 0, 0)) { }
You can see that the window will not respond to anything you do.
One interesting thing I have found by experimenting is that the message loop given below works.
while (GetMessage(&msg, NULL, 0, 0)) { DefWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); }
DefWindowProc is supposed to handle messages which WndProc does not handle. But in the above case, WndProc receives messages and the program appears to behave normally. It is said that the source code of DefWindowProc is included with Windows SDK, but I never had a chance to see it.
If you are convinced of everything I said, try this. Change the line
wcex.lpfnWndProc = (WNDPROC)WndProc;
to
wcex.lpfnWndProc = (WNDPROC)WinMain;
and change the message loop to
while (GetMessage(&msg, NULL, 0, 0)) { }
Now run the program. The program is not supposed to crash because it is DispatchMessage that routes messages to WndProc. But, the program promptly crashes. Why? It turns out that Windows calls WndProc several times directly before reaching the GetMessage loop. It should be clear that while most of the times Windows programs call Windows to get messages, sometimes Windows chooses to send messages directly to WndProc.
Comments, suggestions and corrections regarding the above article are welcome!