Introduction
Since Microsoft's introduction of DirectX, game developers have dealt with the challenge of encapsulating the Win32 windowing API. The obvious choice, really the only ready-made choice aside from Borland's ObjectWindows, was MFC, but its large footprint and inefficient implementation led many developers to either develop their own in-house solution or use the Win32 API directly. Today, however, there is an alternative to MFC known as the Windows Template Library that provides the desired encapsulation without incurring a large footprint or poor performance.
This is the first in a series of articles that intends to introduce developers to the use of this sparsely documented library in a game development context. While development utilities are an obvious application for the WTL, and by the end of this series the reader should have gained enough experience to use it in such a manner, this series' true focus will be on using the WTL to aid in developing the actual game application.
Requirements
I make a few assumptions in this article. First I assume that the reader has a basic understanding of Win32 programming. He or she should be capable of creating a simple Win32 application. No knowledge of MFC, ATL, or WTL will be required. I also assume that the reader is using VC 6.0 SP 5 or VC .NET. Other compilers may work but I cannot make any guarantees. Before continuing with this article you'll also want to make sure you have the WTL 3.1 or 7.0 libraries and have added the WTL\Include path to your list of include directories. The libraries can be obtained from the
MSDN download site.
Active Template Library (ATL)
Before we get into using the WTL we'll need to define what the Active Template Library is and its relation to the WTL. The Active Template Library is a set of template classes developed by Microsoft to aid in the development of ActiveX controls and COM objects. Since many ActiveX controls interact with the user in some fashion, Microsoft implemented classes to encapsulate the Windows user interface elements. ATL's classes were designed to be lightweight and fast making the ATL Window Classes (a.k.a WTL) a very attractive alternative to MFC and straight Win32 API programming. With that out of the way we're ready to begin.
Getting Started
Let's start at the beginning. Create an empty Win32 Application project and add a file to it called
main.cpp. Make it a windows executable that simply returns.
#include
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
return 0;
}
Because the WTL is an extension of ATL and is actually intended to be used in conjunction with developing COM objects / COM servers, there are a few items that need to be added to
main.cpp to appease the ATL library. We'll need to include
atlbase.h and
atlapp.h and define a global variable called
_Module. We'll also add initialization and termination calls for
_Module.
The
_Module variable is used by ATL to help encapsulate the functionality required by COM servers. It's tightly coupled to the ATL libraries so it has to be defined, and its name can't be changed. A typical ATL project would define
_Module as an instance of a
CComModule object. Here we define its type as
CAppModule, a derivative of
CComModule that adds support for message loops.
_Module gives the ATL libraries access to the application instance as well as some information about the operating environment. Let's not worry about the details of
_Module just yet. We'll cover it in more detail in another article. Below is our new
main.cpp file.
#include
#include
#include
CAppModule _Module;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
_Module.Init(NULL, hInstance);
_Module.Term();
return 0;
}
Having made these few changes, we're going to leave
main.cpp alone for a bit. At this point all that's missing is a window, its window procedure, and a message loop. Let's move on and get to know the classes the WTL provides for wrapping and implementing windows and their window procedures.
The CWindow Class
The
CWindow class is nothing more than a light wrapper around an
HWND. It implements most of the standard Win32 window functions and adds a few useful functions like
CenterWindow() and
ResizeClient(). Keep in mind that the
CWindow object and the window itself are separate entities. The construction and destruction of the window is independent of the
CWindow object. Another important thing to note is that CWindow cannot be used to implement a window. It can only be used to wrap existing windows or create windows using an existing window class [2], [3].
To create an edit control using
CWindow we simply use
CWindow's
Create() function.
CWindow EditCtrl;
EditCtrl.Create(_T("EDIT"), hParent, CWindow::rcDefault,_T(""),WS_CHILD);
The first parameter is the name of the window class; in this case "EDIT". The second is the
HWND of the parent window. The third parameter is a
RECT structure defining the size and position of the window. The fourth is the windows caption, and the fifth is the style. There are actually a few more parameters but they provide default values, and this example doesn't need to change these values. For complete documentation see the
CWindow reference on MSDN [3].
If the edit control already existed and we simply wished to use the
CWindow methods to access it, we could use
CWindow's
Attach() method to assign the existing
HWND to the
CWindow object. There's also a corresponding
Detach() method that clears the handle from the
CWindow object. Additionally,
CWindow provides a convenient constructor that takes an
HWND and attaches the
CWindow object to it.
void SomeFunction(HWND hEditCtrl)
{
CWindow EditCtrl;
EditCtrl.Attach(hEditCtrl);
EditCtrl.SetWindowText(_T("This is a Test"));
}
// or
void SomeFunction2(HWND hEditCtrl)
{
CWindow EditCtrl(hEditCtrl);
EditCtrl.SetWindowText(_T("This is a Test"));
}
CWindow also implements
operator= and an
HWND conversion operator. This allows
CWindow objects to be assigned
HWNDs
EditCtrl = hEditCtrl;
and anywhere an
HWND is called for a
CWindow can be used instead.
[xml]CWindow EditCtrl;
EditCtrl.Create(_T("EDIT"), NULL, CWindow::rcDefault,_T(""),WS_CHILD);
SomeFunction(EditCtrl);[/xml]
Don't worry about allowing a
CWindow object to go out of scope while a window handle is still attached; the window will not be destroyed. As mentioned earlier, the lifetime of the
CWindow object and the window itself are independent. To actually destroy the attached window use
CWindow's
DestroyWindow() method.
For complete documentation on
CWindow and its methods see the
CWindow reference on MSDN [3], [1].
The CWindowImpl Class
The
CWindowImpl class, unlike the
CWindow class, actually implements a window. To use
CWindowImpl we define a new class that inherits from
CWindowImpl. This new class must be passed as the first parameter of the template. The second parameter is a base class whose default value is
CWindow. This allows
CWindowImpl to inherit all the methods provided by
CWindow (
CWindowImpl also uses
CWindow's
m_hWnd member to store the
HWND when a
CWindowImpl instance is used to create a window. Because of this dependency the second parameter of the
CWindowImpl template must be
CWindow or a derivative of
CWindow). For the time being we'll leave the second parameter as is. The third and final parameter is a
traits class. The traits class provides information about the window styles used when creating a window [4], [1]. We'll also ignore this parameter for now. Below is the beginning of
CWindowImpl's definition.
template
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits >
{
public:
DECLARE_WND_CLASS(NULL)
??????
Let's add a new file to our project called
mainwnd.h. To it we'll add a class called
MainWnd that inherits from
CWindowImpl. We'll also need to include
atlwin.h. Below is our start of
mainwnd.h.
#include
class MainWnd : public CWindowImpl
{
};
Defining a Window Class
In standard Win32 programming we define a window class by creating an instance of a
WNDCLASSEX structure, setting its members, and passing it to
RegisterClassEx(). Note that when I refer to a window class I'm speaking of the properties set in and registered with the
WNDCLASSEX structure and not a C++ class.
CWindowImpl simplifies this by providing a handful of macros used to help define and register the window class. The
DECLARE_WND_CLASS macro in the
CWindowImpl definition listed earlier is one of these macros. Passing
NULL creates a window class using a random class name. Since
DECLARE_WND_CLASS(NULL) is declared in
CWindowImpl, any classes derived from
CWindowImpl also inherit this functionality. If you want to give your window class a specific name you'll need to override
CWindowImpl's declaration. To do this, simply pass the name you wish to use to
DECLARE_WND_CLASS in your derived class [1], [2]. Go ahead and make this change to
mainwnd.h. Use anything you like for the class name. Our new
mainwnd.h file should look something like the code below.
#include
class MainWnd : public CWindowImpl
{
public:
DECLARE_WND_CLASS(_T("Specific_Class_Name"))
};
We'll spend more time working with window classes later. For now the
DECLARE_WND_CLASS macro is all we need to get our window class defined.
Message Handling
Like all windows, our window will receive messages sent by the operating system. Most of the messages require nothing more than the default processing provided by windows. Others we'll wish to provide custom handling for. In standard Win32 programming this is accomplished through the window procedure associated with the window class.
CWindowImpl provides a more convenient method. It uses a set of macros to map window messages to functions in a fashion very similar to MFC. The message map is begun with the
BEGIN_MSG_MAP macro and terminated with the
END_MSG_MAP macro.
#include
class MainWnd : public CWindowImpl
{
public:
DECLARE_WND_CLASS(_T("Specific_Class_Name"))
BEGIN_MSG_MAP(MainWnd)
END_MSG_MAP()
};
The
BEGIN_MSG_MAP macro takes the name of the class containing it as its only parameter. Sandwiched between the beginning and ending macros are the message handler macros that actually map window messages to functions [1], [2]. There are a number of message handling macros, but for now we'll only be working with one: the
MESSAGE_HANDLER macro. The following line maps the
WM_DESTROY window message to a function called
OnDestroy().
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
For each message that needs handling, add a
MESSAGE_HANDLER macro with the message ID as the first parameter and the name of the function to call as the second. Let's edit
mainwnd.h, and add a
MESSAGE_HANDLER for the
WM_DESTROY message.
#include
class MainWnd : public CWindowImpl
{
public:
DECLARE_WND_CLASS(_T("Specific_Class_Name"))
BEGIN_MSG_MAP(MainWnd)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
Functions mapped with the
MESSAGE_HANDLER macro should have the following definition.
LRESULT SomeFunctionName( UINT, WPARAM, LPARAM, BOOL& );
The first parameter (
UINT) is the message itself (
WM_CREATE,
WM_PAINT, etc.). The second and third are the same
LPARAM and
WPARAM parameters sent to a typical window procedure. The fourth is a flag that indicates whether the message has been handled or not, and is set to
TRUE by default. Setting the value to
FALSE will cause the message to undergo default message processing once the function returns [5].
Let's add our handler for the
WM_DESTROY message to our
MainWnd class. The code below shows how our new
mainwnd.h file should look.
#include
class MainWnd : public CWindowImpl
{
public:
DECLARE_WND_CLASS(_T("Specific_Class_Name"))
BEGIN_MSG_MAP(MainWnd)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled )
{
PostQuitMessage(0);
bHandled = FALSE;
return 0;
}
};
Having implemented our
WM_DESTROY handler, the
MainWnd class is now complete. It doesn't do much. It just posts the quit message when the window is closed, but this is all we need for our first attempt at using the WTL. We're now ready to return to
main.cpp, create our window, and set up the message loop.
Creating the Window
If you were to open
atlwin.h and looked at the definition of
CWindowImpl, you'd find that it only implements one method:
Create(). All other functionality is inherited from
CwindowImplBaseT and
BaseT [2]. The definition of
CWindowImpl's
Create() method is listed below.
HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
UINT nID = 0, LPVOID lpCreateParam = NULL)
As you can see, only the first two parameters are required; the rest provide default values. The parameters themselves are pretty standard. The first is the parent window, and the second is a
RECT defining the size and position of the window. The third is the windows caption. The fourth and fifth specify the windows style, and the sixth is a control ID usually used with child windows like button's and menu's. The last parameter is assigned to the lpParam value of the
CREATESTRUCT structure passed with the
WM_CREATE message [4], [2].
Let's return to
main.cpp and add code to create our window. First we'll need to include
mainwnd.h. Notice that I included it after the definition of
_Module (see listing below). This is necessary to prevent compilation errors since
atlwin.h, which is included in
mainwnd.h, requires that
_Module be defined. Let's also declare a variable of type
MainWnd, called
mainwnd; and add a calls to
Create(),
ShowWindow(), and
UpdateWindow().
#include
#include
#include
CAppModule _Module;
#include "mainwnd.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MainWnd mainwnd;
_Module.Init(NULL, hInstance);
mainwnd.Create(NULL,CWindow::rcDefault,_T("Main Window"),
WS_OVERLAPPEDWINDOW);
mainwnd.ShowWindow(nCmdShow);
mainwnd.UpdateWindow();
_Module.Term();
return 0;
}
Everything here should be pretty straightforward with the possible exception of the value I passed for the second parameter (
CWindow::rcDefault). This is simply a
RECT structure with default values. CWindow exposes it for the sake of convenience [3].
The Message Loop
Moving right along we'll now add our message loop to
main.cpp. The WTL provides a class called
CMessageLoop to simplify adding message loops. This class, along with Module, work together to route messages to our windows.
CMessageLoop exposes a member function called
Run() that actually starts the message loop.
Run() returns the
WPARAM value of the
WM_QUIT message which in turn is the value that was passed to
PostQuitMessage(). I don't want to get too involved with the details of WTL's message loops here; we'll cover them in more detail in part 2 of this series.
We now have a completed, albeit simple WTL application. The listing below shows our new, complete
main.cpp file.
#include
#include
#include
CAppModule _Module;
#include "mainwnd.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
CMessageLoop theLoop;
MainWnd mainwnd;
_Module.Init(NULL, hInstance);
_Module.AddMessageLoop(&theLoop);
mainwnd.Create(NULL,CWindow::rcDefault,_T("Main Window"),
WS_OVERLAPPEDWINDOW);
mainwnd.ShowWindow(nCmdShow);
mainwnd.UpdateWindow();
int nRet = theLoop.Run();
_Module.RemoveMessageLoop();
_Module.Term();
return nRet;
}
WTL vs. Win32?
How does the WTL compare to straight Win32 programming? Comparing the performance of this application with a similar one developed using just the Win32 API would be kind of pointless, as far as speed is concerned. There just isn't enough functionality here to make much of a comparison. Even with additional functionality the point is rather moot. Most of the applications time is spent simply waiting for messages. My release build of the WTL application weighs in at around 40kb, and the Dependency Walker utility reported that it only used the most basic system dll's. A similar Win32 application weighed in at 36kb (about 4kb smaller) and had exactly the same dependencies as the WTL version. This isn't surprising since the WTL application had the bare minimum to begin with. What this 4kb in code size bought us is an object-oriented approach to creating and implementing windows. Considering the increase in maintainability the WTL code has over the Win32 code, I consider this a bargain. Below is the Win32 application I used in my comparison.
Win32app.cpp
#include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
TCHAR szAppName[] = TEXT("Main Window");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("RegisterClass() Failed"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName,TEXT("Main Window"),
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
PostQuitMessage (0);
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
WTL vs. MFC?
So, how about MFC? Again, performance is difficult to measure. But we do see a big difference in code size and dependencies. A similar MFC app actually weighed in less than its WTL counterpart, around 16kb. This is somewhat misleading because a good portion of the code was contained in mfc42.dll. With static linking the MFC app grew to 100kb, and the Dependency Walker still reported dependencies on comctl32.dll and the bloated shell32.dll. Not too big a deal but it does demonstrate the kind of overhead an MFC application is subject to that the WTL application gets to avoid. Below is the MFC application I used for this comparison.
MFCapp.h
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance ();
};
class CMainWindow : public CFrameWnd
{
public:
CMainWindow ();
protected:
DECLARE_MESSAGE_MAP ()
};
MFCapp.cpp
#include
#include "MFCapp.h"
CMyApp myApp;
BOOL CMyApp::InitInstance ()
{
m_pMainWnd = new CMainWindow;
m_pMainWnd->ShowWindow (m_nCmdShow);
m_pMainWnd->UpdateWindow ();
return TRUE;
}
BEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd)
END_MESSAGE_MAP ()
CMainWindow::CMainWindow ()
{
Create (NULL, _T ("Main Window"));
}
Conclusion
In the context of game development our baby application still wants for a few basic features. For starters it'd be nice to replace the standard background window color with black. A mechanism for performing idle processing is also needed. The simple application developed in this article also bothers me in that is doesn't follow the grand tradition of displaying a "Hello World" message. I decided to leave this last item as an exercise for the reader since the new functionality is relatively simple to add. Just add a
MESSAGE_HANDLER entry for the
WM_PAINT message to the message map and use a function like
TextOut() to display the text. The reader is encourage to make any other changes he or she likes. The files (and project) used in this article will not be needed in the articles to follow.
Those who downloaded the WTL 7.0 libraries will find a WTL AppWizard included with the download (I believe the WTL 3.1 download also includes it but can't quite remember). The wizard-generated files make for great example code. And some of the options it provides are just plain cool. See the WTL's readme file for installation instructions.
In
part 2 of this series we'll take an in depth look at the class definition macros, see what exactly it is they do for us, and how to gain greater control of the attributes our
CWindowImpl object uses when registering a window class. We'll also learn how to insert idle processing into our message loop and how to get our hands on messages before they've been translated. In the meantime I can be reached by emailing [email="rmack005@hotmail.com"]rmack005@hotmail.com[/email] or by posting a comment in this articles discussion forum, which I'll check frequently. Comments and suggestions are greatly appreciated. Now go play.
The code accompanying this article will compile with the WTL 7.1 libraries which can be obtained at the
MSDN Download Site.
Acknowledgements
First I'd like to thank Ben "Kylotan" Sizer for the feedback he provided on an early draft of this article. His comments significantly improved its quality. I'd like to thank Olga Perez for her efforts to correct my grammar. You put up a valiant fight, though I'm not sure you won. And of course, I'd like to thank the staff at Gamedev.net for providing both a home and an audience for this article. Thanks again everyone.
References
[1] Michael Park, "
ATL 3.0 Window Classes: An Introduction", MSDN.
[2] Brad King and George Shepherd, "Inside ATL", Microsoft Press, 1999 (Chapter 14)
[3]
MSDN's CWindow Reference
[4]
MSDN's CWindowImpl Reference
[5]
MSDN's MESSAGE_HANDLER Reference
[The Code Project also hosted a semi-official documentation effort for the WTL. http://www.thecodeproject.com/ -Ed.]