I'm back and I have some good news and some bad news. The bad news is that we will probably not work on the DirectDraw wrapper as I said in the last article. The thing is, we need some more (not-so) important skeletons to help us work with DirectX before we can start on the wrapper. One of them is a Win32 skeleton and another is an error handler. I think we need to build these two things first so that we can debug our DirectX application more easily. The good news is that we will have a complete program at the end of this article. It won't do very much, but it will work (or so I hope).
Enough said, let the coding begin.
[size="5"]Error Handling
Before we start on our Win32 skeleton we should create some kind of error visualization system. Since using GDI (graphical device interface) to display text in DirectX isn't very fast, we're going to make have our error routine log errors in a text file instead. We will also add an option to exit the game when an error occurs. We'll create the class [font="Courier New"][color="#000080"]CError[/color][/font] to handle all error routines. The prototype for our class will be the following:
class CError
{
public:
FILE *fErrorLog;
bool bQuit;
LPSTR lpzMessage;
CError (LPSTR, bool);
~CError ();
ProcessError (DWORD);
};
For the C programmers, a class is just like a [font="Courier New"][color="#000080"]struct[/color][/font] with functions; they have more advanced features as well, but we will not use them. Just in case you don't know, a constructor is called when an instance of the class is declared and the destructor is called when the instance is killed, either by the program termination, the variable going out of scope, etc.
We will now start the actual code for the error handling routines. First we must code the constructor and destructor.
CError::CError (LPSTR lpzFileName, bool bQuitOnError)
{
fErrorLog = fopen (lpzFileName, "wt");
bQuit = bQuitOnError;
}
CError::~CError ()
{
}
The destructor is empty for now.
Next is the main core of our error handling. It's very basic for now, but it will grow as we start adding the errors for DirectX.
CError::ProcessError (DWORD dwError)
{
DWORD dwMsgSize;
switch (dwError)
{
default :
dwMsgSize = strlen ("Unkown error...\n");
lpzMessage = (LPSTR) malloc (dwMsgSize + 1);
strcpy (lpzMessage, "Unkown error...\n");
break;
}
if (fErrorLog != NULL)
{
fprintf (fErrorLog, lpzMessage);
}
if (lpzMessage != NULL)
{
free (lpzMessage);
}
if (bQuit == true)
{
if (fErrorLog != NULL)
{
fclose (fErrorLog);
}
PostQuitMessage (dwError);
}
return 0;
}
I just want to add two comments about this function. First, whenever we want to add another error we should put the code before the [font="Courier New"][color="#000080"]default[/color][/font] case; we will do this later. Second, we don't do any checking to see if all went well with the error handling. This is your homework. Check to see if the memory is allocated correctly, if the file was opened successfully, etc. Try to do this on your own, and if you can't then [email="akura@crosswinds.net"]e-mail[/email] me and I'll help you out. I'll also post the corrections in the next article.
We will now need an instance of the class.
CError ErrorHandling ("errors.log", true);
[size="5"]The Win32 Skeleton
Many game programmers I know don't really bother learning the basics of Windows programming. They just copy-paste an existing skeleton the got from someone else and write their game on top of it. Even though there is nothing really wrong with that, you will be limited by the skeleton. I'm not going to teach you much about the Win32 API, but I'll teach you enough to put you on the right track to create windows the way you want them to appear.
char szClassName [] = "Chapter2";
char szWinName [] = "Chapter2";
LRESULT CALLBACK WndProc(HWND,UINT, WPARAM, LPARAM);
For any DOS or Unix programmer, your C/C++ program always starts with [font="Courier New"][color="#000080"]void main ()[/color][/font], or if you want command line arguments [font="Courier New"][color="#000080"]void main (int argc, char *argv[ ])[/color][/font]. In Windows it starts with [font="Courier New"][color="#000080"]WinMain[/color][/font], which has a few more parameters than you may like. You must also include [font="Courier New"][color="#000080"]windows.h[/color][/font] in your files.
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASSEX wcl;
bool bRunning;
ZeroMemory (&wcl, sizeof (WNDCLASSEX));
wcl.cbSize = sizeof (WNDCLASSEX);
wcl.hInstance = hInst;
wcl.lpszClassName = szClassName;
wcl.lpfnWndProc = WndProc;
wcl.style = 0;
wcl.lpszMenuName = NULL;
wcl.cbClsExtra = NULL;
wcl.cbWndExtra = NULL;
wcl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wcl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wcl.hCursor = LoadCursor (NULL, IDC_ARROW);
wcl.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
We need another couple of variables. The first is the message to be processed, the second is the handle for the window, and the third one is where we will hold the window class information. The variable [font="Courier New"][color="#000080"]bRunning[/color][/font] will tell us if the game is running or not.
We then set up our window class. The variable names are quite easy, so I'll just cover the not so obvious ones. [font="Courier New"][color="#000080"]cbSize[/color][/font] is needed to let Windows know the size of the class when registering. [font="Courier New"][color="#000080"]lpfnWndProc[/color][/font] is the message handler. [font="Courier New"][color="#000080"]cbClsExtra[/color][/font] and [font="Courier New"][color="#000080"]cbWndExtra[/color][/font] are extra properties of the class, which in this case are set to nothing.
if (!RegisterClassEx(&wcl))
{
ErrorHandling.ProcessError (ERROR_REGISTER_CLASS);
return (-1);
}
case ERROR_REGISTER_CLASS :
dwMsgSize = strlen ("Chapter 2 - Error log file\nCould'nt register class...\n");
lpzMessage = (LPSTR) malloc (dwMsgSize + 1);
strcpy (lpzMessage, "Chapter 2 - Error log file\nCould'nt register class...\n");
break;
Back to [font="Courier New"][color="#000080"]WinMain[/color][/font], we need to create our window and show it. We set the class name, window name, type of window ([font="Courier New"][color="#000080"]WS_OVERLAPPEDWINDOW[/color][/font] is the standard window with a title bar, minimize/maximize box and close box), and position and size (0,0, 640,480). We set the parent as the desktop, supply no menu, use the current instance and use [font="Courier New"][color="#000080"]NULL[/color][/font] as the last parameter (advanced functions).
hWnd = CreateWindow (szClassName, szWinName, WS_OVERLAPPEDWINDOW,0 , 0, 640, 480,
HWND_DESKTOP, NULL, hInst, NULL);
ShowWindow(hWnd, nCmdShow);
while (bRunning)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
bRunning = false;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
}
}
return 0;
}
Is that it? No, we still need the message handler.
[size="5"]The Message Handler
LRESULT CALLBACK WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc (hWnd, msg, wParam, lParam);
break;
}
return 0;
}
We determine the type of message and how to handle it by using a [font="Courier New"][color="#000080"]switch[/color][/font] statement. This simple program just uses [font="Courier New"][color="#000080"]WM_DESTROY[/color][/font], which is a message that is sent to the program when it's beilg closed. We handle the message by telling the program to quit and [font="Courier New"][color="#000080"]return 0[/color][/font] to let Windows know we processed the message.
All messages that aren't processed by us are returned to Windows to use the default processing by calling [font="Courier New"][color="#000080"]DefWindowProc[/color][/font].
[size="5"]Conclusion
This was a long tiring article. I hope you were able to understand everything we covered. If you have any problems compiling the source, working through the material or any suggestions/corrections, feel free to [email="akura@crosswinds.net"]mail[/email] me.
Ohh! Just one more thing, one month before school ended, I finally got a job in the industry and I got a new e-mail account. Feel free to use [email="akura@crosswinds.net"]akura@crosswinds.net[/email] to contact me.
We will finally (I promise) dig into DirectDraw in the next article. Until then, stay well folks.