præclarum

Search:

Frank A. Krueger
Seattle, WA, United States

Recent Posts

Times Reader makes reading on the web enjoyable ag...
A Naive Implementation of MapReduce in C#
Saturday
My 20 Minute Experience with WinFX
Patterns of Integer Usage
Knuth’s Solution to the Permutation Problem in C#
Simplifying the Interview: Solving Permutation Pro...
Using SQLite as an On-disk File Format, Part 2
Using SQLite as an On-disk File Format, Part 1
Travel Guide to India

Assimilated by Win32

Sunday, May 27, 2007 Link

This is a repost of an article I wrote back in March 2006. It seems to have fallen through the cracks as I moved from my own software to Blogger. Here it is for your enjoyment.


I always regarded straight Win32 development (that is, GUI app development using no wrappers over the Win32 API) as too time-consuming and tedious. Because of this, I have taught myself the big six: MFC, ATL, WTL, VCL, WinForms, and WPF (not at all in that order).

Nowadays I find myself using WinForms a lot. Even when not using the interactive designer of Visual Studio, WinForms makes a lot of GUI development trivial. However, there is a price you have to pay in order to use WinForms and the WPF; that price is managed code.


Now I love managed code. It provides a safe execution environment, a million classes that do two-million things (referring to the .NET library), garbage collection, excellent multi-language interoperability, and a very simple machine against which to write compilers. Let me repeat, I love managed code. But that’s not what this article is about.

Sometimes I feel the need or am forced to write unmanaged code. Even worse, sometimes I feel the need or am forced to write unmanaged GUI code. When it comes to native GUI code, I have a choice between MFC, ATL, WTL, VCL, or straight Win32. Prior to a month ago Win32 wasn’t even an option. It’s too ugly, it’s too old-school (where are my classes?), and nobody does it. Well, those were my opinions.

This last year, I have spent way too much time reading through all of The Old New Thing, Mark Russinovich’s Sysinternals Blog, Joel on Software, and Larry Osterman’s Blog (among others). These guys are old-school to be sure.

Old-school, in this instance, means not using C++’s (or C#’s, or Java’s, or Eiffel’s) object-oriented style of programming. You know the type: information hiding, single-object virtual function dispatch, lots of smart pointer, RAII, horrible function composition through bind, blah, blah, blah. No, instead, they tend to utilize more imperative function-based approaches. This is the style of coding that makes the source code of Project Oberon, and Quake (I, II, and III), and MMIXware, and the Linux Kernel, and SICP (and SICM), such a pleasure to read. They are devoid of the noise of modern object-oriented-programming languages, and contain nearly pure signal. This is also the style of the Win32 API.

Anyway, the point is that I have been thinking a lot about increasing the signal-to-noise ratio of my own code. I began to consider whether I could meet such an end by coding straight to the Win32 API. So I embarked on an experiment to write an application using straight Win32 and to compare that experience with writing WinForms apps.

I’ll summarize my conclusion: I like Win32 development. The Win32 API isn’t nearly as bad as I once thought, and some parts of it even scream out “I was designed very well!” This is not the conclusion I expected. I expected to get 1/4 of the way through development and to abandon the idea as just another waste of time.

So I was wrong, and I even look forward to the next time I get to write a native Win32 application. This article describes what I found along the way and the style of code that I eventually adopted that made development a breeze.


Let’s Begin with WinMain.


Open Visual Studio, create a new Win32 Project (without MFC or ATL) and you will get 8 files with the main C++ file consuming nearly 200 lines of code. Not
a great start at all. This is a part of the intimidation of Win32 projects. If it takes 200 lines and 8 files of overhead to create the base application, then how large is the Win32 overhead of the real application going to be?


As it turns out, not much overhead at all. These 200 lines, while foreign and a bit clumsy looking, aren’t bad at all. There are four functions generated for you (ignore the silly About function). These are WinMain, MyRegisterClass, WndProc, and InitInstance.


The first function WinMain is your entry point and the controller of the main GUI thread of your application. It is responsible for registering your window classes (more on those in just a moment), for creating the main (top-level) windows of the application, and for dispatching the messages for those windows. That’s just about all that your WinMain should do, and that’s all that the generated WinMain does do. There are of course some other trivial matters to accomplish such as initializing various services (Winsock, COM, CommonControls, etc.) but we only need to add that as necessary.


Object-oriented Windows.


So what about this registering window classes stuff? As it turns out, and this is the largest mental block to get through when first developing Win32 applications,
the windows API is object-oriented. That is, it has a notion of classes and instances. Every window on the screen has a class and that class dictates the behavior of that window and, often times, its child windows.


The class of the window, specifically, defines a few superficial traits: icons, cursors, and default background brushes, some esoteric traits: class styles, and “extra” information, and the most important trait: what to do when the window receives messages.


If we think about the Win32 notion of windows in terms of the C# object-oriented model, then the API defines an abstract Window class has one abstract method: HandleMessage. When you wish to create a new window class, you simply need to define a new class that overrides this function. To summarize in code, the OS would define the abstract window as:



class Window {
public virtual Result HandleMessage(Message msg) {
// Default processing of msg
}
}

The application developer would then define their own windows as:

class MyWindow : Window {
public override Result HandleMessage(Message msg) {
// Special processing of msg
}
}

The Win32 API is nearly identical to this setup with the only restriction that new window classes can only derive from Window and none of its (derived) base classes. Of course, the Win32 syntax is quite different too. Instead of the class definition of MyWindow above, one would instead write:

ATOM RegisterMyWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = MyWindow_HandleMessage;
wcex.hInstance = hInstance;
wcex.lpszClassName = L"MyWindow";
return RegisterClassEx(&wcex);
}

Here, the function MyWindow HandleMessage overrides the default message handler (DefWindowProc) of the window. The registration process is a bit more verbose than the C# definition, but they are equivalent. Also, I should note that this is an incomplete registration, some important field assignments to wcex have been neglected to keep parity with the C# sample. Both the Win32 procedure and the C# class definition would have to be expanded to include all the options that
Win32 allows for window classes.

In C#, one would create a window by instantiating an object of the appropriate type (class). That would look something like:


Window wnd = new MyWindow(...);

In the Win32 world, window creation is just as simple:

HWND hWnd = CreateWindow(L"MyWindow", ...);

Again, I’m simplifying things, but the point is clear: the Win32 system is a class based system with one polymorphic function, the WndProc.

Once a window is created, the HandleMessage (or WndProc) function is responsible for defining the behavior of that window. Behavior here includes how the window responds to the user, how it responds to the OS, and how it displays itself.

It does all of this using the simple and robust method of message passing. When you want to tell a window to do something, you send it a message telling it what to do. When you want to get some information, you send it a message asking for information. When the window needs to tell other windows what to do (usually child windows), it sends them messages.

In the C# WinForms world, you add a bunch of public methods to a window class and invoke those methods in order to accomplish the same tasks. Message passing and function invocation are two means to the same end. Again we see the roots of OOP in the Win32 API.

In fact, the authors of SICP uses message passing to implement an object-oriented system. Since the Scheme programming language is quite a bit more powerful than the C programming language (against which the Win32 API has been designed), the Win32 message system isn’t nearly as rich as the SICP system; however, the ideas are the same. (Message passing can also be used to implement a very robust form of data sharing and control in concurrent applications. The programming language Erlang makes excellent use of this mechanism.)

Let’s take a step away from all these generalities now and answer the important question: how can I use the Win32 API to create rich applications with minimal effort.

My idea is to go with the flow. If the Win32 API is based around window classes and messagepassing, then let’s make full use of those ideas. Our assumption is that if we simply allow ourselves to be assimilated by the API, then we should find no resistance to using it and application development will become easier.


Assimilated Win32 Development.


The first step to coding a GUI application is to determine what classes of windows are needed. If I was writing a text editor, I may choose to create a new
window class that is superior to the built-in Edit and RichEdit classes of Windows. I could then use this “SuperEdit” wherever the user needed to enter data. If I was writing a simulation package, then I would develop a “WorldView” window class that could display some physical manifestation of the objects I was simulating. If I was writing a strip-chart viewer (think heart monitor, or seismograph, or lie detector), then I would create a class of window tailored to displaying strip-charts.

These are the types of things that the VCL refers to as components, and what WinForms refers to as controls. Me? I’ll just call them windows.

The first order of business for creating a new window class is to write the RegisterXClass function, where X is the class name of the window. This will be very similar to the one shown above. Refer to the documentation of WNDCLASSEX to learn of all the wonderful options you get.

Next, we need to implement some WndProc for that window. This function can begin simple and be added to as more functionality is required of the window. So we begin with a simple one:



LRESULT CALLBACK X_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
default: return DefWindowProc(hWnd, msg, wParam, lParam);
}
}

We have now defined a window that does nothing more than emulate the default window. It accomplishes this by deferring all responses to the default WndProc. In order for the window to do something more interesting, we need to handle more messages.

As I mentioned briefly before, the Windows message system isn’t very rich. In fact, messages only carry three bits (not literally) of information: the message type, and two parameters, wParam and lParam. Because of this limitation, the true contents of a message are encoded into those two parameters. For instance, if you need to pass a complicated structure as a part of the message, then you can pass a pointer to that structure as one of the parameters. Windows makes use of this
and many, many more techniques for passing data with messages. For this reason, nearly every message has its own unique encoding based upon its type.

This is one of those things that at first makes Win32 coding seem so tedious. You end up spending a lot of time in MSDN figuring out just what the exact encoding of each message really is.

This must have gotten really old really fast for the Windows developer’s too since they ship a header file with the SDK that simplifies this whole process. The header file windowsx.h contains a macro for each of the pre-defined windows messages. This macro automatically decodes the parameters and invokes an event handler for each message. This makes writing the WndProc a breeze and leaves the developer only with the challenge of coding the logic of the handlers.

Let’s look at an example use of these macros. We know that we will need to override keyboard input to the window since a window that doesn’t react to the user is usually pretty annoying. To override the key input, we can handle the WM_KEYDOWN message.

Open windowsx.h and search for WM_KEYDOWN. There, we see a comment and a macro
definition:



/* void Cls_OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags) */
#define HANDLE_WM_KEYDOWN(hwnd, wParam, lParam, fn) \
...


The first commented line is a prototype of a function to handle the message. The second line begins a macro that will call that function from our WndProc.


We copy that prototype and paste into our own code to create our event handler:



static void X_OnKeyDown(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
...
}

This function will contain any process that should be taken whenever the user presses or hold a key down. Within switch statement of the WndProc for the window, we also have to add this simple line:



HANDLE_MSG(hWnd, WM_KEYDOWN, X_OnKeyDown);


The macro HANDLE_MSG will be sure to invoke the macro HANDLE WM KEYDOWN and
that macro will call our X OnKeyDown function. All messages will have a similar invocation of the HANDLE_MSG macro.


You should continue to repeat this process for each of the messages you wish to handle. Doing so will result in a very clean and simple WndProc and a set of event handlers that exactly match the messages you wish to handle.


Window State.


A window usually has some state associated with it. Text boxes must remember
their text; strip-charts must have access to the data to display; etc. Given the method for implementing a window class given in the previous section, we are never given a chance to record our state. (And let’s not even contemplate the idea of storing the state as a bunch of global variables. If we did that, then we would only be able to instantiate one instance of our window class. This defeats the whole purpose of using classes in the first place.)


Because we need the state of the window to be attached to each instance of a window, and because instances of a window are represented by HWNDs, we had better associate our state with the HWND. Fortunately, there is a very easy way to do this. All windows have a pointer-sized amount of memory in which the user (here, the programmer) can place anything they want. The function SetWindowLongPtr allows us to write to that pointer-sized area, and GetWindowLongPtr allows us to retrieve the value there.


Let us then create a single struct that contains all of the state of our window. When the window is created, we will allocate and initialize this data and store a pointer to it using SetWindowLongPtr. Then, whenever we need access to our state, we can use GetWindowLongPtr to retrieve it.



Here is a complete example of a window (well, minus the registration function) that implements such a scheme for storing its state.



struct X_State {
int c;
};
template
BOOL CreateAndSaveState(HWND hWnd)
{
BOOL fSuccess = FALSE;
T *pt = (T*)calloc(1, sizeof(T));
assert(pt);
if (NULL != pt) {
SetLastError(0);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pt);
if (0 == GetLastError())
fSuccess = TRUE;
else
free(pt);
}
return fSuccess;
}
static BOOL X_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
return CreateAndSaveState(hWnd);
}
static void X_OnDestroy(HWND hWnd)
{
free((X_State*)GetWindowLongPtr(hWnd, GWLP_USERDATA));
}
LRESULT CALLBACK X_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
HANDLE_MSG(hWnd, WM_CREATE, X_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, X_OnDestroy);
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
}

In this example, we have a window with a single integer as its state. It creates and remembers this state when it is created by handling the WM CREATE message. I have given a complete version of the handler for that message, X OnCreate. This handler is robust in the sense that if there is any problem recording the state of the window, then the creation of the window is terminated all together. When the window is destroyed, the state is also destroyed.


Because the OnCreate handlers for all our windows will be pretty similar (they only differ in the type of state), I utilize a template function that handles the nitty gritty of creating and storing away the state. Please keep in mind that this template is only valid for plain old data (POD) structs and classes; that is, for structs and classes that do not have constructors, destructors, virtual functions, or contain classes with constructors, destructors, or virtual functions. If you wish
to make it a little more C++ friendly, then the calls to calloc and free should be replaced with new and delete. I preferred to use calloc and free simply because I wanted a guarantee that the code would not throw an exception. Exceptions are great in functional code, but really create disasters in imperative code.


Also, don’t forget that you’ll probably want to put a call to PostQuitMessage in the OnDestroy handler of your main window. If you do not do this then your application process will keep chugging along with no visible windows! That is not very user-friendly thing to do.


Commanding through Messages.


We have dealt with the definition of a window’s class and its
storage of state. Only one element remains: the control of the window by code. Up until now, our window operated solely on its own; to make it function cohesively with the rest of the application, we have to have a way to communicate with it. Again, we use messages to communicate with windows.

We can define our own message types for communication with our window. These messages mirror member functions of classes. We can have message for setting and retrieving state, and we can have messages that force the window to perform more complex operations. In the case of the strip-chart, we may define a message such as SC PAUSE that causes the display to stop updating with new data. In the case of the world view simulation window, we may define a message such as WV_ZOOM in order to zoom in and out.


Defining new messages is easy. We simply create a constant (either through a macro or a C++ const declaration) for the message type. The only stipulation that Windows places on these new messages is that their ordinal value should be no less than WM_USER.


So we could go ahead and state things such as:



#define SC_PAUSE (WM_USER+1)
#define WV_ZOOM (WM_USER+2)


And, with that, we have created new messages.


Our lives only become a little more complex when we decide to associate some data with the messages. We need to encode that data into the two parameters wParam and lParam. Life is simplest if we just bottle up the data into a struct and pass a pointer to that struct as the lParam. A lot of the Win32 functions do this, and it is pretty simple.


We must only force ourselves to make our own lives easier by writing HANDLE_* macros,
similar to those found in windowsx.h, for our own messages. This way our WndProc will have one uniform structure that is easy to interrogate and maintain.


We can also make the lives of the user of our window easier by implementing simple wrapper macros around SendMessage. Windowsx.h does this for the standard controls (see for example Edit GetLineCount amongst others) and it is a real time saver. Essentially these wrapper macros are defining a functional interface over the message passing system. We get the best of both worlds!


Putting it all Together.


Now that we have defined exactly what functions and macros we need
to create for our window classes, let’s talk briefly about code organization.


Under the principle of encapsulation, I try to put as much code as I can in its own translation unit (cpp file) and declare the functions as static so that they cannot be mistakenly called.


This means that each window class will have its own translation unit. In that unit, all functions (except one) will be marked as static. The WndProc will be static along with all the functions it calls. The one exception to this rule is the class registration function. This must necessarily be visible to the application code so that it can be called at initialization time. A prototype of the function will also be given in a header file for the window.


The message constants and macros of the previous section will also need to be contained in this header file. And that is it. The public header should be short and sweet: the register function, and the message macros. The state struct should not be in the header. That should exist only in the translation unit.


Following these rules creates a situation where all windows’ data are safely tucked away, and the clients of these windows have simple and strict means of interfacing with them.


Conclusion.


This article details my best understanding of the path of least resistance when
developing Windows GUI programs. In the past I have tried a variety of class libraries to make GUI development easier. What I have realized over time is that these libraries usually don’t make anything easier and typically reduce the signal to noise ratio of my own code. With that in mind, I have set a few simple rules to follow when developing these applications. When these rules are followed, I find Win32 development to be a simple and enjoyable.

Reader Comments

Thank you, Frank, for your clear and concise (very unlike some other Win32 docs) overview. As a *nix C developer, I'm very glad to have your writeup as I am impelled (arm still in socket) to do some W32 development.

Posted at 10:06 AM by pete

I'm glad you found it useful. Certainly C++ pgramming to the Win32 API isn't at all intuitive, but with just a bit of practice (and a little C++ magic to help you out), you'll get the hang of it.

Posted at 8:11 PM by Frank A. Krueger

Post a Comment