Using the Windows Template Library Part 1: Getting Started

Published January 15, 2004 by R.Mack, posted by Myopic Rhino
Do you see issues with this article? Let us know.
Advertisement

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.]
Cancel Save
0 Likes 0 Comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement