The WinMain function is the entry point of the application. When a user double clicks on your executable Windows carries out some initialisation code then passes control to this function. Its just like main() in C. In this function you should set up your application and then enter a loop that will continue until the application is closed. We will look at all the steps:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
Straight away we see some of those Windows types. APIENTRY resolves to _stdcall which is used for all windows API calls. HINSTANCE is a new type, its a handle to an instance. LPSTR is Microsoft's way of saying 'long pointer to a string' it normally resolves to char*. A description of each parameter is shown below:
For more details see the MSDN entry here: WinMain
In the WinMain function we want to set up our application. We want to create a main window (we can create more later if we want) and go into a message loop.
Windows allows many styles of window to be created. To tell Windows to create a window as you want it you need to define a class of window and register it with windows. The class can be reused later if you wish. In order to register a class you call the API function: ATOM RegisterClassEx(CONST WNDLCASSEX *lpwcx);
This function takes a pointer to a structure you have defined to describe your window class - more on that in a minute. The return value ATOM is one of those Microsoft typedefs again and resolves to unsigned short. This function returns 0 if there was an error. The const indicates that the function will not change the structure.
So we need to fill a WNDCLASSEX structure with a description of how we want our window. Below is an example of this:
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style= CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc= (WNDPROC)WndProc;
wcex.cbClsExtra= 0;
wcex.cbWndExtra= 0;
wcex.hInstance= hInstance;
wcex.hIcon= 0;
wcex.hCursor= LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName= 0;
wcex.lpszClassName= "MyWindowClass";
wcex.hIconSm= 0;
// Now we can go ahead and register our new window class
RegisterClassEx(&wcex);
This is quite a common way of communicating with an API. We declare an API defined structure, fill some values in, and pass it to the API via a call. It avoids having to make functions that take many parameters.
There follows a quick look at the elements in WNDCLASSEX we can play with:
For more information on the WNDCLASSEX structure see MSDN here: WNDCLASSEX
Now that we have registered our window class with Windows we can go ahead and create the window. When creating the window we can apply additional styles that affect the way the window is shown. You decide if you want borders, menus, system menu, close box, resize bar etc. The choice is up to you. After successfully creating a window the API returns a handle to that window. The handle is a unique identifier that we can use in later calls to API functions that apply to this instance of our window class. Earlier we saw that HINSTANCE was a handle to the application instance, well HWND is a handle to a window instance.
The create window API call is:
HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName,DWORD style,int x, int y, int width, int height, HWND hWndParent,HMENU hMenu,HANDLE hInstance,LPVOID lpParam);
and an example is:
HWND hWnd = CreateWindow("MyWindowClass", "The title of the Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
In Microspeak LPCTSTR is a long pointer to a const string, or const char * to the rest of us. DWORD is a double word which is an unsigned long on a PC. LPVOID is a Long Pointer to a Void or void* to us. (A void pointer has its uses but beware of its dangers. The advantage is that you can cast any pointer to a void* pointer which allows functions to deal with different types).
A break down of the parameters in this call:
* - Windows will fill in default x, y and w, h values for you if you wish. If you want this then instead of entering an exact value here you instead write CW_USEDEFAULT. This is useful as you do not know what resolution a user of your program has their desktop set to.
For more information on this call see the MSDN entry: CreateWindow
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
Note: these calls will issue initialisation messages to your Window which you can trap in the WndProc. Keep this is mind as many people do not realise this happens at this point.
Windows sends some messages directly to your callback function but others are placed in a queue. These tend to be keyboard messages or other input that may need translating. We enter a loop that checks for queued messages. Of one is found it is translated and dispatched to our windows procedure.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
The first parameter is a pointer to a message structure Windows will fill for us, the second parameter is the handle of the window we want to receive messages from or NULL for all our windows and the last two parameters allow us to filter messages by range. The defaults above are used in 99.9% of all cases.
When we receive the message we call TranslateMessage which converts virtual key messages (used for localisation purposes) into character messages and then passes the result on to DispatchMessage which sends the message to the windows procedure we defined in our window class. If GetMessage returns 0 our application is exiting and we drop out of the bottom of WinMain and the program closes. This happens when a WM_QUIT message is received.
The return value from WinMain should be the message wParam if the program closes normally otherwise it should be 0 if the message loop is never entered.
return (int)msg.wParam;
So that's it for WinMain. To summarise: WinMain is the entry point for our application, we create and register a window class and then call CreateWindow. We remember to get Windows to show the window and then we enter a message receive / dispatch loop until our application is finished. So the only remaining thing to do is to write our WndProc, the callback function that receives messages sent to our window.
Note: you will see nothing until you have a WndProc in place!
The next function: WndProc