WinMain - application entry point
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:
- HINSTANCE hInstance - when your program runs it is considered one instance of the executable running. You could in fact run many, creating multiple instances, and this value would be different for each e.g. you could open ten calculators. Since the instance handle uniquely identifies your application it is used in Windows API calls elsewhere, so it is common to make a global copy of this.
- hPrevInstance is always NULL in modern version of Windows so we can ignore it.
- LPSTR lpCmdLine - when you run a program you can start it with a set of commands. If you look at shortcuts in Windows for programs there is a space where you can put a command line. This is commonly used for things like starting the program in a debug mode etc. in games it is often used for commands like 'Cheat' or 'Test' etc.
- int nCmdShow - This is a flag describing how the window should be shown to start with. Examples include SW_SHOWMAXIMIZED and SW_SHOWMINIMIZED.
For more details see the MSDN entry here: WinMain
Creating a Window
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.
Registering a window class
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:
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style= CS_HREDRAW | CS_VREDRAW;
wcex.hCursor= LoadCursor(NULL, IDC_ARROW);
// Now we can go ahead and register our new window class
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:
- style - this allows us to combine some flags to describe the low level style for our window, CS_HREDRAW means we want our window redrawing if the width is ever changed.
- lpfnWindProc - this is essential, this is where we tell Windows which of our functions is going to handle messages sent to this window. Remember from earlier that Windows is event driven, so if someone clicks on the window Windows responds to that even by telling us what has happened. We provide a function to receive these messages and in this structure element we pass the pointer to the function (a callback function). The WNDPROC is used to cast the function pointer into the correct form for Windows.
- hIcon and hIconSm - defines the icons used by the program. The small one (16 by 16) is displayed in the top left of the window and on the taskbar. The big one (32 by 32) is visible when you look at the executable in explorer on your hard drive and also when you ALT-TAB between applications. These icons are defined using the resource editor ,described elsewhere, and referred to using a #define.
- hCursor - this describes the default mouse cursor displayed whenever the mouse is over this window. Usually this is an arrow (IDC_ARROW) but you can also have things like IDC_CROSS or IDC_WAIT (the hourglass) or even define your own.
- hBrBackground - the colour you want the background of your window to be. By using COLOR_WINDOW+1 we are saying we want to use whatever background the user of the PC has defined. If we wanted to use a basic colour we need to define a brush or use one of the stock brushes. e.g. to set the background to black we could use: hBrBackground=(HBRUSH) GetStockObject(BLACK_BRUSH); Note that if you are drawing the whole screen yourself you are better to leave this as 0 which means Windows will not fill the background.
- lpszMenuName - the menu you want to be displayed in this window. Again you define this in the resource editor and give it an identifier or 0 if no menu used.
- lpszClassName - this is essential, this is your name you want to give to this class, it can be anything you want.
For more information on the WNDCLASSEX structure see MSDN here: WNDCLASSEX
Creating the Window
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:
- lpClassName - this is class name the window is to be based on. In here we enter the name we just gave to the class we registered with Windows.
- lpWindowName - this is the title of the window that will appear in the bar at the top.
- style - this is the style of window we want. It is the result of combining a number of flags together. You can create all sorts of different styles of window here. A common one is WS_OVERLAPPEDWINDOW which is a combination of flags defining a window that has a caption, a system menu, a thick frame and a minimise and maximise box. There are lots of combinations possible, other things include scroll bar, dialog frame etc. Look in the MSDN help for all the possibilities.
- x,y - this is the position in this windows parent window of the top left of the window. If the window has no parent it is the x, y position relative to the desktop. *
- width, height - simply the required width and height of the window to start with. *
- hWndParent - if this is a child window of another window you need to give the parents handle here, otherwise use NULL.
- hMenu - if your window uses a menu you will have defined it in the resource editor. If you leave this entry as NULL the class menu will be used (see above) otherwise you can change it to use a different menu.
- hInstance - the program instance that was passed to us.
- lpParam - this is a pointer to anything you want to pass to your windows procedure when the window is created. Normally NULL.
* - 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
Showing The Window
A common mistake is to forget to tell Windows to display the window - it has caught me out a few times. So to tell Windows to display the window and update it you need to call the following functions:
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.
The Message Loop
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.
while (GetMessage(&msg, NULL, 0, 0))
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.
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