Creating a C++ Window Class
One of the common urges that a C++ programmer starting Windows applications programming has is the desire to encapsulate the creation of a window and a window class in a C++ class. Unfortunately, this isn't a straightforward process. Usually the first sign of trouble is the inability to assign a normal C++ member function to the lpfnWndProc member of the WNDCLASS.
// usual first attempt class MyWindowClass { public: LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); // other stuff }; // somewhere else WNDCLASS wc = {}; wc.lpfnWndProc = &MyWindowClass::WndProc; // error, incompatible pointer types
The reason for this problem is that non-static member functions have a different calling convention than normal functions. Member functions need to be aware of the this pointer for the class, which normal function pointers cannot handle. So in order to use a normal member function to handle Windows messages you need to create a non-member or static member function that you can assign to lpfnWndProc. That function then needs to figure out the right pointer for the HWND that gets passed and then call the member function to handle the Windows message on that pointer.
There are a number of different schemes to associate a C++ object pointer with a handle. One method is to create some sort of map object that associates HWNDs with object pointers such as a std::map or a hash table of some sort. This can be done either per thread or globally. The method I use in the code for this article is to associate the object pointer with the user data of the window instance.
As part of the call to CreateWindow() or CreateWindowEx() you can specify a pointer parameter as the lpParam argument of the functions. When the WM_NCCREATE and WM_CREATE messages are sent to the window, the lpParam argument of the CreateWindow() or CreateWindowEx() call is passed as the lpCreateParams member of the CREATESTRUCT. Since the WM_NCCREATE is sent before WM_CREATE, in the handler for WM_NCCREATE I call SetWindowLongPtr() to put that pointer in the user data portion of the window instance with the GWLP_USERDATA flag. After that, all the message handler needs to do is grab the pointer from the user data with GetWindowLongPtr() (again with the GWLP_USERDATA flag) and call the right member function on the fetched pointer. However, WM_NCCREATE usually isn't the first window message sent, so the static window procedure needs to account for that.
class MyWindowClass { public: void WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if (LONG_PTR user_data = GetWindowLongPtr(hWnd, GWLP_USERDATA)) { MyWindowClass * this_window = reinterpret_cast<MyWindowClass *>(user_data); return this_window->WndProc(hWnd, Msg, wParam, lParam); } if (Msg == WM_NCCREATE) { LPCREATESTRUCT create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam); void * lpCreateParam = create_struct->lpCreateParams; MyWindowClass * this_window = reinterpret_cast<MyWindowClass *>(lpCreateParam); SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this_window)); return this_window->WndProc(hWnd, Msg, wParam, lParam); } return DefWindowProc(hWnd, Msg, wParam, lParam); } }; // somewhere else WNDCLASS wc = {}; wc.lpfnWndProc = &MyWindowClass::StaticWndProc;
To simplify the logic used by the static window procedure function, I use two windows procedures. The first one's job is to wait until WM_NCCREATE is called and then place the pointer in the user data of the window instance. Once it does that, it changes the windows procedure to the second window procedure, which only takes the pointer from the user data and calls the member function that handles the actual messages. It does this with SetWindowLongPtr() and the GWLP_WNDPROC flag.
class MyWindowClass { public: void WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK InitialWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if (Msg == WM_NCCREATE) { LPCREATESTRUCT create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam); void * lpCreateParam = create_struct->lpCreateParams; MyWindowClass * this_window = reinterpret_cast<MyWindowClass *>(lpCreateParam); SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this_window)); SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&MyWindowClass::StaticWndProc)); return this_window->WndProc(hWnd, Msg, wParam, lParam); } return DefWindowProc(hWnd, Msg, wParam, lParam); } static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { LONG_PTR user_data = GetWindowLongPtr(hWnd, GWLP_USERDATA); MyWindowClass * this_window = reinterpret_cast<MyWindowClass *>(user_data); return this_window->WndProc(hWnd, Msg, wParam, lParam); } }; // somewhere else WNDCLASS wc = {}; wc.lpfnWndProc = &MyWindowClass::InitialWndProc;
This may not look simpler, but it reduces the responsiblity of the static window procedure, which in turn translates into better branch prediction for the CPU.
Source code to a complete sample application is available here.
The sample application presented utilizes Microsoft's generic text mappings. Most Windows API functions and structures come in two versions: a narrow character version and a wide character version. These are also known as ANSI and Unicode versions. For example, CreateWindow() is actually two functions: CreateWindowA() and CreateWindowW(). A macro transformation maps CreateWindow() calls to one of these two functions depending on what preprocessor definitions are in effect. If UNICODE is defined, CreateWindow() is actually CreateWindowW(). If it isn't defined CreateWindow() is actually CreateWindowA().
The difference between the two versions is that the ANSI versions use CHARs for their character type in strings. The wide character versions use WCHARs for their character types. In order to write code that compiles cleanly with both UNICODE defined and it not defined, you can use the generic text mappings. The type generic text mappings are a series of macros that, like CreateWindow() and other Windows API functions and structures, change their definition based upon whether or not UNICODE is defined.
For example, the macro TCHAR changes between CHAR or WCHAR or LPCTSTR is either LPCSTR or LPCWSTR. Wrapping string and character literals with the TEXT() or _T() macros will create narrow character literals when UNICODE isn't defined and wide character literals when it is. To use the generic text mappings, you first use these types and macros instead of using types like CHAR or LPCSTR and you need to include the tchar.h header.
However, the process isn't completely transparent. There are several places where the generic text mappings break down. For instance, the transformations are not defined on C++ standard library classes and functions. In some places you can use the template parameterization of the C++ standard library classes to your advantage. For example, in the sample program I use the typedef:
typedef std::basic_stringstream<TCHAR> tstringstream;
This is equivalent to a std::stringstream when UNICODE isn't defined and a std::wstringstream when it is. You can define similar typedefs for std::basic_string but that abstraction breaks down for other types such as the file stream classes, which have functions that require narrow character string arguments and the exception classes which also only take narrow character string arguments, hence why there are non _T() or TEXT() wrapped string literals used as exception constructor arguments in the source code. Fortunately the C++ ostream classes provides for automatic widening when a narrow chararacter string is inserted into a wide character ostream.
Most compiler by default do not define the UNICODE preprocessor symbol. However, Microsoft Visual C++ .NET 2005 does define UNICODE by default.