C++ Win32 API Simple GUI Wrapper
How would you like to write your Windows Graphical (Win32 GUI) software as EASY as this?
MyWindow wnd(hInstance, TEXT("My Window Title"), wndClass.className ());
wnd.Create();
wnd.Show();
Easy programming in Win32 isn't it?
toc_collapse=0;
Contents
- Simple C++ Win32 API Wrapper Tutorial
- The Problem
- Window Wrapper
- The Window Class Wrapper
- Your New Simple WinMain
Simple C++ Win32 API Wrapper Tutorial
You should understand C++ class inheritance and polymorphism, that is, abstract classes, virtual functions, pure virtual functions.
You should know that this tutorial assumes you have a thorough understanding of C++ and a basic understanding of Win32 API.
You'll notice the use of the C++ scope resolution operator when calling Win32 API functions such as ::CreateWindowEX () or ::RegisterWindowEx(). That's just a habit of mine when calling these kind of functions, basically it tells C++ to search the functions in the global namespace.
How would you like to write your Win32 programs like this?
MyWindow wnd (hInstance, TEXT("My Window Title"), wndClass.className ());
wnd.Create ();
wnd.Show ();
/* message loop */
Everyone knows that creating a window is not an simple task but a rather tedious one, so I'm going to try and make it easier. This method is very similar to Qt, Java, or WxWidgets.
The Problem
If you ever tried wrapping a window's functionality I bet the first thing you asked yourself was: "What am I going to do with the window procedure?". I also bet this was the first answer that crossed your mind: "I'm just going to declare it as a member function within my window wrapper class and then I'm gonna pass it to the WNDCLASSEX structure as the lpfnWndProc field."
Well, unfortunately that won't work. Why? Because member functions have an additional hidden parameter, the this pointer, so instead of LRESULT CALLBACK wndProc (HWND, UINT, WPARAM, LPARAM) your member function will be LRESULT CALLBACK memberWndProc (YourClassName *this, HWND, UINT, WPARAM, LPARAM).
Obviously you won't be able to do the assignment wcx.lpfnWndProc = memberWndProc because the two function pointers are of different types.
So how do we solve this? We'll use a static member function that will act as a message router. A static member function can be called without creating an instance of the class, therefore it doesn't have the this pointer in its parameter list, allowing us to pass it as the lpfnWndProc member in WNDCLASSEX.
Message router you say? Yes, because the window procedure is going to be a static member function, we can't redefine its behavior in derived classes so we're gonna use a little hack. We're gonna declare another window procedure function that will be implemented in derived clasess and will do the actual message processing particular to each window. The static message router is going to determine which instance's window procedure to call by associating the window's HWND handle with a pointer to the derived class instance. This will be explained later.
Window Wrapper
First we create an abstract window class, let's call it AbstractWindow. Later, we are going to create our own windows by deriving this class and defining some of its member functions.
class AbstractWindow {
protected:
// window handle
HWND _hwnd;
// ... other members ...
public:
AbstractWindow () {}
// this will be WNDCLASSEX::lpfnWndProc
static LRESULT CALLBACK msgRouter (HWND, UINT, WPARAM, LPARAM);
// this is the actual window procedure
// this will be implemented in derived classes and will be called by msgRouter
virtual LRESULT CALLBACK wndProc (HWND, UINT, WPARAM, LPARAM) = 0;
// calls CreateWindowEx, creating the window
virtual bool Create ();
// ... other member functions ...
};
Notice the pure virtual function wndProc, this is the actual window procedure that will be implemented in derived classes, the static msgRouter function only determines the instance of AbstractWindow to send the message to and calls its wndProc. How?
- In the AbstractWindow::Create () method, we're gonna pass the this pointer as the last parameter of CreateWindowEx().
- Then, CreateWindowEx() sends it to msgRouter through a WM_NCCREATE message. (Details: the pointer is stored in the LPARAM parameter as a LPCREATESTRUCT structure, in the lpCreateParams field. Not important)
- We then handle the WM_NCCREATE message and associate the AbstractWindow pointer with the window's HWND using SetWindowLong ().
- Then, we retrieve the AbstractWindow pointer using GetWindowLong () and call its wndProc method that will do the actual processing.
So basically that is what will happen for every newly created window. For an existing one, when messages arrive in msgRouter, we'll have the window's handle which we'll use to retrieve the pointer to its AbstractWindow object that contains its window procedure, then we'll forward the message there.
Here's the code:
#include "AbstractWindow.h"
bool AbstractWindow::Create ()
{
// we'll just assume CreateWindowEx ()'s parameters are protected members of AbstractWindow
_hwnd = ::CreateWindowEx (
_styleEx,
_className,
_windowName,
_style,
_x,
_y,
_width,
_height,
_hwndParent,
_hMenu,
_hInstance,
this // pointer to this class instance
);
if (!_hwnd) return false;
else return true;
}
LRESULT CALLBACK AbstractWindow::msgRouter (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
{
AbstractWindow *wnd = 0; // pointer to the window that should receive the message
if (message == WM_NCCREATE) {
// if this message gets sent then a new window has just been created,
// so we'll asociate its handle with its AbstractWindow instance pointer
::SetWindowLong (hwnd, GWL_USERDATA, long((LPCREATESTRUCT(lParam))->lpCreateParams));
}
// --- messages different from WN_NCCREATE / or WM_NCCREATE was just processed ---
// we retrieve the instance of AbstractWindow that corresponds to the destination window's HWND
wnd = (AbstractWindow *) (::GetWindowLong (hwnd, GWL_USERDATA));
// we then route the message to the wndProc method defined in the derived AbstractWindow class
if (wnd)
wnd->wndProc (hwnd, message, wParam, lParam);
else
// for messages that arrive prior to WM_NCCREATE
// and the HWND <-> AbstractWindow * association was not made
return ::DefWindowProc (hwnd, message, wParam, lParam);
}
Now the only thing left is to derive a class from AbstractWindow and define its wndProc method. Before doing that I'm gonna show you how to wrap the WNDCLASSEX structure so you can easily create a Window Class that uses the AbstractWindow::msgRouter as its window procedure.
The Window Class Wrapper
Before creating a window we must first create a Window Class. I'm hoping that by this time you know what I mean by a Window Class. The following C++ class will wrap a Window Class more precisely the WNDCLASSEX structure, the other one above wrapped a window.
There are several ways of doing this, but the only thing each implementation must have in common is setting the lpfnWndProc field of WNDCLASSEX to AbstractWindow::msgRouter, because that's the place we want all the messages for our windows to go.
So let's call this class SimpleWindowClass. Basically, the only input the class' constructor needs is the application's HINSTANCE and the Window Class name.
This would be one way of implementing it:
class SimpleWindowClass : protected WNDCLASSEX {
public:
SimpleWindowClass (HINSTANCE hInst, const TCHAR * className);
// registers the class
virtual bool Register ();
//retrieve the class name
virtual const TCHAR * className () const { return lpszClassName; }
protected:
// --- all WNDCLASSEX's members are protected, so they can be inherited by derived classes ---
};
The class inherits from the WNDCLASSEX structure, has a virtual member function that retrieves the Window Class' name, a Register() member function that calls the RegisterClassEx() Win32 function to register the window class, and a constructor that will fill in the WNDCLASSEX structure.
Here's the implementation:
// include the AbstractWindow header,
// we need it for the lpfnWndProc = AbstractWindow::msgRouter assignment
#include "AbstractWindow.h"
#include "SimpleWindowClass.h"
SimpleWindowClass::SimpleWindowClass (HINSTANCE hInst, const TCHAR *className)
{
// could've used GetModuleHandle (NULL) instead of passing the instance as a parameter
hInstance = hInst;
// all messages for windows belonging to this Window Class will get sent to msgRouter
lpfnWndProc = AbstractWindow::msgRouter;
lpszClassName = className;
// --- set default values for the rest of the WNDCLASSEX structure ---
// --- later you can derive your own class and modify this behavior ---
lpszMenuName = 0;
cbSize = sizeof (WNDCLASSEX);
cbClsExtra = 0;
cbWndExtra = 0;
style = 0;
hIcon = ::LoadIcon (NULL, IDI_APPLICATION);
hIconSm = ::LoadIcon (NULL, IDI_APPLICATION);
hCursor = ::LoadCursor (NULL, IDC_ARROW);
hbrBackground = (HBRUSH) ::GetStockObject (COLOR_BTNFACE);
// --- the constructor won't call the Register () member function ---
// --- that was my choice, again, you can change the behavior in your code ---
}
bool SimpleWindowClass::Register ()
{
if (::RegisterClassEx (this)) // we pass the this pointer because our class inherits from WNDCLASSEX
return true;
else
return false;
}
Great! We've just simplified the Window Class creation from manually filling a WNDCLASSEX structure and calling RegisterClassEx() to declaring a SimpleWindowClass object and calling its Register() member function.
Now let's create a SimpleWindow class that will inherit from AbstractWindow and implement the wndProc member function allowing our window to "work".
4. Create windows by subclassing AbstractWindow
The only thing left to do now is to inherit from AbstractWindow and create our window's behavior by defining the wndProc pure virtual function.
Let's start:
#include "AbstractWindow.h" // include the AbstractWindow header file, needed for inheritance
class SimpleWindow : public AbstractWindow {
public:
// the constructor takes two arguments, the window's title, and the Window Class' name
// the Window Class must be registered using the SimpleWindowClass object
// you can retrieve the Window Class name using the SimpleWindowClass::className () method
SimpleWindow (const TCHAR *windowName, const TCHAR *className);
// this is our window's procedure, you're gonna implement it like any other window procedure
virtual LRESULT CALLBACK wndProc (HWND, UINT, WPARAM, LPARAM);
// shows the window on the screen and updates its client area
void Show () {
::ShowWindow (_hwnd, SW_SHOW);
::UpdateWindow (_hwnd);
}
};
Now we're gonna define the constructor and the window procedure. In this case the constructor is just going to set default values for CreateWindowEx()'s parameters.
#include "SimpleWindow.h"
SimpleWindow::SimpleWindow (const TCHAR *windowName, const TCHAR *className)
: AbstractWindow ()
{
// --- we're going to assume the following members are declared in AbstractWindow as protected ---
_windowName = windowName;
_className = className;
_hInstance = ::GetModuleHandle (NULL); // or you could've passed the HINSTANCE as a constructor parameter
_style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
_y = _x = CW_USEDEFAULT;
_height = _width = CW_USEDEFAULT;
_styleEx = 0;
_hwndParent = 0;
_hMenu = 0;
// --- again those were supposed to be protected members of AbstractWindow ---
}
// our window procedure, this is were we define our window's behavior
LRESULT CALLBACK SimpleWindow::wndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
::MessageBox (hwnd, TEXT("Window has been successfully created"), TEXT("Succes"), MB_OK);
return 0;
case WM_DESTROY:
::PostQuitMessage (0);
return 0;
default:
return ::DefWindowProc (hwnd, message, wParam, lParam);
}
}
By this time we've drastically reduced WinMain's size.
Your New Simple WinMain
In the new WinMain we'll just create a Window Class by creating a SimpleWindowClass object and calling its Register() member function, then we're gonna create a window by creating a SimpleWindow object, and calling its Create() and Show() member functions. Of course we're also going to need a message loop.
#include "SimpleWindow.h" // create a window
int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// create the Window Class
SimpleWindowClass wndClass (hInstance, TEXT ("My window class"));
wndClass->Register ();
// create the window,
SimpleWindow wnd (TEXT ("Generic Window Title"), wndClass->className ());
wnd->Create();
// show the window on the screen
wnd->Show ();
// pump messages:
MSG msg;
int status;
while ((status = ::GetMessage (&msg, 0, 0, 0 )) != 0)
{
if (status == -1) {
// handle the error, break
break;
}
::TranslateMessage (&msg);
::DispatchMessage (&msg);
}
return msg.wParam;
}
That's all there is to it. From now on there are endless possibilities. You can modify your window wrapper or your Window Class wrapper the way you want them to work.
An interesting thing you could do is add message handlers, such as OnCommand (), OnCreate (), OnPaint () and so on. You would need to define their default behavior in the AbstractWindow class and then in the msgRouter you would forward each message to its handler. Then in your derived classes you would only define handlers for the messages you'd like to handle, the rest will be handled by the base class, that is, AbstractWindow. The final result would be an MFC-like window class, with member functions for each message, this way you would get rid of the window procedure for ever.
Let me show you what I mean:
class AbstractWindow {
// --- previous class definition here ---
public:
virtual bool OnCommand (int ctrlId, int notifyCode) { return false; }
virtual bool OnDestroy () { ::PostQuitMessage (0); return false; }
virtual bool OnClose () { return false; }
// --- and so on, add as many handlers as necessary ---
// --- and define their default behavior ---
// --- usually they return 0, but check MSDN for details ---
};
/* ---- AbstractWindow.cpp ---- */
LRESULT CALLBACK AbstractWindow::msgRouter (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
{
AbstractWindow *wnd = 0;
if (message == WM_NCCREATE) {
wnd = (AbstractWindow *) ((LPCREATESTRUCT(lParam))->lpCreateParams);
::SetWindowLong (hwnd, GWL_USERDATA, long(wnd));
wnd->OnNCCreate (/* params */);
} else {
wnd = (AbstractWindow *) (::GetWindowLong (hwnd, GWL_USERDATA));
if (wnd) {
switch (message)
{
case WM_COMMAND:
return OnCommand (LOWORD (wParam), HIWORD(wParam))
case WM_DESTROY:
return OnDestroy ();
case WM_CLOSE:
return OnClose ();
// --- and so on, it'll be a lot of work to do,
// but most apps don't use every window message ---
default: // for the messages you did not define any handlers
return ::DefWindowProc (hwnd, message, wParam, lParam);
}
}
else // for messages that arrive prior to WM_NCCREATE
// and the HWND -> AbstractWindow * association was not made
return ::DefWindowProc (hwnd, message, wParam, lParam);
}
}
In your derived class instead of defining the old long window procedure you will only define handlers for the messages you'd like to handle, the rest of the messages will either be handled by their default handler implementation in AbstractWindow or by DefWindowProc() in msgRouter.
// --- rest of code here ---
// --- constructors, private data, etc etc...
public:
// there is no need for a window procedure now, just add handlers.
virtual bool OnCommand (int ctrlId, int notify Code) { /* do stuff */ }
virtual bool OnCreate () { ::MessageBox (_hwnd, TEXT("Window created"), TEXT ("Success"), MB_OK); }
// and so on, every message you want to handle must have a default handler in AbstractWindow
// and you're going to implement it here, change its behavior the way you needs
};
Enjoy!

Jesse (not verified)
Thanks, this helped a lot!
Thanks, this helped a lot!
Anonymous (not verified)
it'd be nice if there's a
it'd be nice if there's a downloadable code
Post new comment