Coupling a window handle with a data object
Andreas Jönsson, July 2002
In this tutorial I will show you how to couple a created window handle with a data object even before the window has received any messages. This is good for the case where you want to have data driven windows, i.e windows that behave according to the data associated with them. By using data driven windows you can create two windows with different behaviour without having to register two different window classes, which is how the Win32 API normally works.
I use this technique for coupling all my windows and dialogs with a C++ class so that I can move the message handler into the C++ class instead of having it in the window procedure. This allows me to easily create derivable windows without the clumsiness of Win32 API.
Window creation
We will use normal Win32 API functions for creating our windows, so I hope that you have some knowledge of this already as I'm not going into detail on that in this article. If you need a refresh you can read my other tutorial that deals with that topic: A simple window application.
The usual way of coupling a window handle with a data object is to do it when the window receives a WS_NCCREATE or WS_CREATE, which are some of the first messages the window receives. The problem with this method is that not all types of windows receive these messages where you can handle them. Dialogs, for example, handle these messages internally and only send messages to your message procedure after the window has already been created.
We need to find some way of obtaining the window handle even before the first message is sent to the window. Luckily the Win32 API gives us this opportunity through window hooks. A window hook is a method that allows interception of Win32 API actions. These can be used for monitoring actions inside the OS and even change the input or output for API functions. In our case we will use it to intercept the creation of the window so that we can store the handle together with a data object for the coupling.
The method used for registering a hook procedure is SetWindowsHookEx() and it registeres a user-defined function that will be called when specified actions occurs. Use UnhookWindowsHookEx() to remove the hook when it is no longer needed. I've written a simple function that does both the registering and unregistering.
HHOOK hCreateHook = 0; cWindow *wndCreator = 0; int HookCreate(cWindow *wnd) { if( wnd != 0 ) { wndCreator = wnd; hCreateHook = ::SetWindowsHookEx(WH_CBT, CreateProc, 0, ::GetCurrentThreadId()); if( hCreateHook == 0 ) { // Failed, use GetLastError() to know what happened return EWND_HOOK_FAILED; } } else { if( hCreateHook && !::UnhookWindowsHookEx(hCreateHook) ) { // Failed, use GetLastError() to know what happened return EWND_HOOK_FAILED; } hCreateHook = 0; wndCreator = 0; } return 0; }
The cWindow class is a simple windows wrapper class that I've written, but you can use whatever data structure you want here. Note that this function has not been created to be thread safe. If you want to make your implementation thread safe you'll need to put the global variables in a thread specific data object, so that each thread has their own copy of the variables.
As you have probably noticed HookCreate() registers a function CreateProc() that will be called when the window is created. CreateProc() is the function that does the actual coupling of the data object with the window handle. It will also automatically remove the hook so that other windows won't be coupled to the same data object by mistake.
LRESULT CALLBACK CreateProc(int nCode, WPARAM wParam, LPARAM lParam) { if( nCode == HCBT_CREATEWND ) { // Connect the new window with the class instance HWND hWnd = (HWND)wParam; CBT_CREATEWND *cw = (CBT_CREATEWND*)lParam; if( wndCreator ) ::SetWindowLong(hWnd, GWL_USERDATA, (LONG)wndCreator); // Unregister the hook procedure here HookCreate(0); // Return 0 to allow continued creation return 0; } return ::CallNextHookEx(hCreateHook, nCode, wParam, lParam); }
It is important to remember that CallNextHookEx() must be called at the end of your hook procedure if you don't handle the call, this is because you never know if anyone else has registered a hook procedure as well.
In this implementation I store a pointer to the data object in the window handle's GWL_USERDATA member by calling SetWindowLong(). If you are using this member for something else, then for example std::map could be used instead.
Using the coupled data object
When you create your window you first call HookCreate() with a pointer to your data object. There is no need to call HookCreate() again to remove the hook after the window has been created since CreateProc() already did this for you.
void cWindow::Create() { HookCreate(this); HWND hWnd = ::CreateWindowEx( styleEx, className, title, style, xPos, yPos, width, height, hParent, hMenu, hInst, createParam ); }
You will also need to register a window class with a message procedure that takes advantage of the coupled data object. My suggestion is that you register a generic window class and then call a member function of the data object to do the actual message processing. The registered generic window procedure would then look something like this:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { cWindow *wnd; // Extract the object from the window handle. wnd = (cWindow *)::GetWindowLong(hWnd, GWL_USERDATA); if( wnd ) { // Let the object handle it. return wnd->MsgProc(msg, wParam, lParam); } // If no class instance is found we call the default procedure return ::DefWindowProc(hWnd, msg, wParam, lParam); }
Conclusion
I hope that this tutorial has given you some new knowledge and that you will find the Win32 API more easy to work with. It is my belief that the Win32 API is not so difficult to work with once you do a little wrapping of more complicated/tedious tasks. In my opinion the raw Win32 API is more easy to work with than MFC, even though MFC has Wizards to help out.
As always, any comments or questions on this article are welcome.
Further reading
Platform SDK, Microsoft