|
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.
|