flipCode - Tech File - Jaap Suter [an error occurred while processing this directive]
Jaap Suter
(aka XjaapX)
Click the name for some bio info

E-Mail: J.Suter@student.utwente.nl



   06/15/2000, Win32 Programming


Hello everybody,

I might have promised some things in the past about where my next techfile update would be about. I'm going to break that promise. Sorry, I suppose I really suck. But seriously, I had this tutorial lying around in three editions and I decided to add them all up into one techfile update for your pleasure.

Note that this tutorial was a three-part short update at first. I changed the text a little bit to fit better, but if you can find out where each edition ends and starts you win a free full year supply of linker errors.

This time I would like to give an introduction to Win32 programming with C++. We will do this by creating the simplest application possible, a window that pops up and has the standard maximize, close and minimize buttons. This is al you ever need for a standard windowed or fullscreen DirectX application. Maybe it's also what you use for OpenGL but I'm not familiar with that.

I will not discuss compiler dependent stuff in here. Personally, I'm a huge fan of visual studio but many people out here use other good compilers. The only thing your compiler has to be able to do is compile Win32 programs. Most nowadays compilers can do this, except if you don't use Windows ofcourse.

If you need a good free Win32 compiler, I suggest taking a look at LccWin32. I believe that it was called that way. Do a search on the internet for it. Only drawback is that it's a C compiler so no OOP for you :). But for this tutorial you won't need OOP anyway.

I suppose you are all familiar with the main function. This function is the application entry point in dos, and most other operating system, applications. Note: I'm aware of the fact that you can define other application entrypoint functions if you feel freaky, but I'm talking about the defaults here.

A Win32 program doesn't use the main function but another function as the entrypoint. This function is called the WinMain. The declaration is listed below...

int WINAPI WinMain(  HINSTANCE hInstance,  
                     HINSTANCE hPrevInstance,  
                     LPSTR lpCmdLine,      
                     int nCmdShow          
                  );
First let's take a look at the return value and that weird WINAPI declaration. The function returns an int value. This value is zero when everything is ok. If things aren't ok the value is different. What this value is precisely we will see next week or the week after that. Not let's take a look at the WINAPI macro. This macro is defined somewhere in windows.h, I believe. It means something like __far __pascal. This has to do with the pushing on the stack of the parameters. Normally in C++ this is done from right to left yet with the pascal convection this is done from left to right. Correct me if I'm wrong. It also has something to do with who is responsible for stack cleaning, I should look it up. It doesn't matter anyway cause you won't need to know how it works actually, just put it in front of your WinMain function. Let's get to the parameters.

HINSTANCE hInstance

This parameter is the handle to the current instance of this application. It is used for all sorts of windows dealings. Think of this as a way to indentify the application. You can use it for example if you want only one instance of your application to be running at once. (Try to start Winamp twice and you will see it won't work.) For now, it's not very important.

HINSTANCE hPrevInstance

Handle to the previous instance. The funny thing is that for a win32 program this handle is (almost?) always NULL. I guess that it's there for future compatibility. Or maybe it's a leftover from the win16 days. It doesn't matter anyway cause we won't need it.

LPSTR lpCmdLine

This is a pointer to a nullterminated string that contains the command line. This string is without the application executables name itself. I rarely use command line parameters and I doupt that you will. I'd rather click an icon instead of using "run" from the start menu. It's a matter of taste I guess.

int nCmdShow

This specifies how the window should be shown. For example it can be maximized immediately or minimized at startup. But since you don't have control over this variable it's not interesting at all.

It looks like the WinMain file isn't very interesting. That's true. I normally set it up in five minutes and dump all win32 handling in it and quickly switch to a fullscreen directX app. We need a lot more then a WinMain function though. In a moment we will register and create a window and show it. And after that we will handle messages.

So far I showed only what the WinMain function looked like. Soon we will be able to compile something. This is going to be a bit boring but some things just have to be done.

Here's the WinMain function again.

int WINAPI WinMain(  HINSTANCE hInstance,  
                     HINSTANCE hPrevInstance,  
                     LPSTR lpCmdLine,      
                     int nCmdShow          
                   )
{
    // Let's put something in here today;
}
The first thing a Win32 program has to do is registering and creating a window (Afterall the OS is called Windows :). The below code will do just that.

First we register the class

WNDCLASS windowClass;        // Declare a windowsClass structure;
 
// Fill the structure with relevant info;
windowClass.lpszClassName     = "Simple WindowClass";
windowClass.lpfnWndProc       = MainWindowProcedure;
windowClass.style             = CS_VREDRAW | CS_HREDRAW;
windowClass.hInstance         = hInstance;
windowClass.hIcon             = LoadIcon( NULL, IDI_APPLICATION );
windowClass.hCursor           = LoadCursor( NULL, IDC_ARROW );
windowClass.hbrBackground     = (HBRUSH)( COLOR_WINDOW+1 );
windowClass.lpszMenuName      = NULL;
windowClass.cbClsExtra        = 0;
windowClass.cbWndExtra        = 0;

// And register at Bill's place;
RegisterClass( &windowClass );
Now let't take a moment to look at the windowClass structure members.

lpszClassName        is the name of your Window Class. Nothing special.
lpfnWndProc          is a pointer to the window procedure. This procedure     
                     handles messages that come from the system. We will
                     take a look at this procedure in a moment and in more 
                     depth next week.
style                Specifies the windowClass style. Only the above mentioned 
                     flags are important. Just set them :).
hInstance            This one you get from the WinMain parameter.
hIcon                This specifies the icon that is drawn on the taskbar. I     
                     have never used my own before, so I just specify a default 
                     system icon.
hCursor              This is the shape of the mouse cursor when the mouse is 
                     above your window. I just use a default one.
hbrBackground        This is the background color of your window. Just set it 
                     the way I did too cause we are drawing on top of it anyway,
                     so it doesn't matter what color it is.
lpszMenuName         I always set this one to NULL cause I don't use standard 
                     windows menus. I'd rather create my own creative interface.
cbClsExtra           Just set it to zero. Mail me if you really desperately 
                     want to know what this member is for.
cbWndExtra           Same as above.
Once you have filled in all these members (it's a though job, I know) you pass the structure to the RegisterClass function. Did I say you have to include "windows.h" already? No? Well just do it.

Anyway, we have registered our window class now, on to the creation of the window itself. See the code below:

HWND windowHandle;            // Declare a handle to the window;
 
// And create the window itself;
windowHandle = CreateWindow( "Simple WindowClass",
                             "Simple Application",
                             WS_OVERLAPPEDWINDOW,
                             0,    
                             0,
                             CW_USEDEFAULT,
                             CW_USEDEFAULT,
                             NULL,
                             NULL,
                             hInstance,
                             NULL
                           );
Let's take a look at the parameters of the CreateWindow function.

LPCTSTR lpClassName   This is the same as the name you register the window class
                      with. 
LPCTSTR lpWindowName  Name of the application as it appears on the window title
                      bar. Be creative with this one :).
DWORD dwStyle         Style that the window gets displayed in. I always use the 
                      above flag WS_OVERLAPPED_WINDOW cause it gives me all I 
                      want. I have no need for a scrollbar, menu etc. If you 
                      need a list of all flags just mail me.
int x                 Initial window positon in pixels. I use zero here.
int y                 Initial window positon in pixels. I use zero here.
int nWidth            Initial window width in pixels. I use the default here.
int nHeight           Initial window height in pixels. I use the default here.
HWND hWndParent       Set it to NULL. Handle to the parent Window. We only have 
                      one window so it isn't neccesary.
HMENU hMenu           Set it to NULL. We don't use menus.
HANDLE hInstance      This one you get from the WinMain parameter.
LPVOID lpParam        Set it to NULL. Not used.
Now you have created the window it will not be visible yet. You have to call ShowWindow as follows...

ShowWindow( windowHandle, nCmdShow );
You pass the windowHandle you just created and the nCmdShow parameter you got from the WinMain. After that call your window should be visible.

The next step is entering the message loop of your window. This is done using this code...

MSG message;    // Declare a message struct
while( GetMessage( &message, NULL, 0, 0 ) ) {
   DispatchMessage( &message );
}
In a minute we will change the above code using PeekMessage instead of GetMessage but for now this will do. Why you might ask? Well, GetMessage waits for messages to arrive. Now this may work for a word processor that doesn't do anything while you type, but for our game that needs any proccesing time it can get this won't do.

GetMessage basically gets the message and fill the message struct. Then you pass this message to the DispatchMessage function that passes it to the WindowProcedure.The windowprocedure then handles the messages. I will get to the implementation of the WindowProcudure in a minute. (Yes we have to do that ourselfs :).

We are now finished with our WinMain function so we can return zero. The WinMain returns zero if everything went fine.

Now let's take a look at the WindowProcedure. Here's the code.

LRESULT WINAPI MainWindowProcedure( HWND hWnd, 
                                    UINT msg, 
                                    WPARAM wParam,  
                                    LPARAM lParam)
{    
    return( DefWindowProc( hWnd, msg, wParam, lParam ));
}
This is the most simple WindowProcedure you can implement. You basically let the default handler (DefWindowProc) handle all your messages. For now this will do,but next week I will go into the WindowProcedure in more depth.

One important thing to notice is that application closure isn't dealt with here. Soon we will solve this problem. The problem is this. Create this program and then run it. You will see a window with nothing in it. You can close this window the normal way. Once you have closed this window it will be removed from the taskbar as well. Next press Ctrl, Alt, Delete and take a look at all running processes. Our application is still in there. That is not a good thing!!!

Now we will solve this.

So we set up a window and displayed it on the screen. I also showed you that our application didn't clean up very nicely. We will fix this by implementing a correct windowprocedure. Here we go.

First of all we are going to use PeekMessage instead of GetMessage. I explained why above. Here is the old code we used.

// Enter the message Loop; 
while( GetMessage( &message, NULL, 0, 0 ) )  
{      
    TranslateMessage( &message );      
    DispatchMessage( &message ); 
}
And we simply change it into:

bool bQuit = false;     // When this one is true the application wants to quit; 
// Enter the main game Loop; 
 while( !bQuit ) 
 {      
     // Handle the messages;      
     while ( PeekMessage( &message, NULL, 0, 0, PM_REMOVE ) )      
     {           
         TranslateMessage( &message );           
         DispatchMessage( &message );      
     }       
     
     // Do all game related stuff here;       
     
     // Quit when ESC is pressed;      
     
     if ( GetAsyncKeyState( VK_ESCAPE ) )       
     {           
         bQuit = true;      
     } 
 } 
This code isn't that hard at all. All we do is check for messages each frame, and after that just goon doing other game related stuff. Basically what we have done now is created the good ol' dos main game loop. Just put your stuff in there the way you did and everything should work.

When you press the escape key (note that I don't use directInput at the moment, since I believe it's not important at the moment) the application quit, and this time it really quits. However, when you close the window only, the application doesn't quit (Which is seen by pressing control alt delete). We need to fix this. And that is where the MainWindowProcedure comes in. First we declare a global enumerator with the following content:

// -----------------------
// Global message enum
enum tag_MessageEnum
{ 
 APPLICATION_RUNNING, 
 APPLICATION_ACTIVATED, 
 APPLICATION_DEACTIVATED, 
 APPLICATION_QUIT, 
 APPLICATION_RESIZE, 
 APPLICATION_MOVE,
} g_MessageEnum; 
Basically there is a message for every Win32 message that is important to us at the moment.On a side note, note that I use a global variable which is considered a death sin among many coders. I did it this way since the beginning, and I think the use of a global variable is justified over here. If anybody knows a better way then I really want to know!

Now we change the main loop to the following...

// Quit when ESC is pressed or the window is closed;
if ( (GetAsyncKeyState( VK_ESCAPE )) || (g_MessageEnum == APPLICATION_QUIT) )
{  
    bQuit = true;
} 
Now the last thing we need to do is set the g_MessageEnum to APPLICATION_QUIT when the window is being closed. This is done by changing the WinMainProcedure to the following code:

LRESULT WINAPI MainWindowProcedure( HWND hWnd,
                                    UINT msg,             
                                    WPARAM wParam,            
                                    LPARAM lParam )
{         
      switch ( msg )         
      {              
          case ( WM_CLOSE ):  
           g_MessageEnum = APPLICATION_QUIT;               
           DestroyWindow( hWnd );               
           return 0;              
          break;                     
      }             
      
      return( DefWindowProc( hWnd, msg, wParam, lParam ));
}
What we are doing here is the following. We let the Default window procedure (DefWindowProc) handle all the messages except the ones that are done by the switch statement. At the moment we are only handling WM_CLOSE but in the future we will handle more.

In the WM_CLOSE handling we set the g_MessageEnum to APPLICATION_QUIT. We call DestroyWindow, which is a win32 api function that does what it says :). And after that we return 0, which is the value you should return when you handle the message succesfully.

And we are finished now. When you compile this program (click here to download) you see you can close it by pressing esc, or by closing the window. It also is removed (really closed) when you press control alt delete.

Actually we are done with windows programming for the moment. The win32 application program we have at the moment has all we need to do DirectX for the moment.

So start up that DirectX SDK help and get some kickass demos going.

If you have any questions do not hesitate to mail me!!

And if any Win32 Guru spots errors in this tutorial then mail me! I'm not an expert on Win32 and I know just enough of it to setup my DirectX applications. I would be thankfull.

Seeya

Jaap Suter





  • 08/22/2000 - Tech File Update
  • 06/15/2000 - Win32 Programming
  • 09/18/1999 - What you asked for...
  • 07/06/1999 - Beamtree Optimizations

  • This document may not be reproduced in any way without explicit permission from the author and flipCode. All Rights Reserved. Best viewed at a high resolution. The views expressed in this document are the views of the author and NOT neccesarily of anyone else associated with flipCode.

    [an error occurred while processing this directive]