|
How it Works
|
|
Win32++ ClassesThe following diagram illustrates the classes used in Win32++:
The classes which define the library itself are contained within the
Using Win32++The code which forms the basis of Win32++ is located in the Win32++ directory.
You shouldn't need to modify these files, but rather inherit from the Win32++
and add any additional code in your derived classes. To create a SDI frame window, for example, you
would typically derive your own class from A separate view window is placed over the client area of the frame
window. Typically, this view window is created by inheriting a class
from One of the important advantages of programming directly with the Windows API is that the code produced is portable, which is to say that it can be compiled on different compilers. The code in this library has been checked for compatibility with Visual C++ 6.0, Visual Studio .NET 2003, Visual C++ 2005 Express Edition, Visual C++ 2008 Express Edition, and also Dev-C++ version 4.9.9.2. Dev-C++ is a free C++ compiler and Integrated Development Environment available for download from here. The library is also compatible with Visual C++ Toolkit 2003 (a free compiler from Microsoft) and Borland's free Turbo C++ 2006. A tutorial which provides step by step instructions for using Win32++ is available here.
Object oriented approachThe key to bringing an object oriented approach to programming directly with the Windows API is to have a C++ class that can create a window and which includes its own window procedure as a member function. Once we have this class, we can inherit from it and override the window procedure member function to handle messages the way we want for each derived window type. Creating a class like this is not trivial, and I suspect that's one of the reasons why MFC was created in the first place. The problem stems from the way a "window class" is registered before the window can be created. (The term "class" here refers to the Windows API "window class", which is not the same thing as a C++ class.) The following code snippet shows how a window class might be registered using the API: WNDCLASSEX wc; memset((WNDCLASSEX*)&wc, 0, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); //The name of the window procedure wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = "TEST"; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //Register the window class ::RegisterClassEx(&wc); Note that we need to supply the function name of our window procedure. The window procedure is where we control what is to be done when a window message is received. This function must conform precisely to the predefined standards required by the Windows API. A typical declaration of the callback function looks like this: LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);
We might be tempted to set the We can make the class TestStatic
{
public:
int member;
void NormalFunction()
{
//We can access member variables in a normal
//member function
member = 5;
//The following line is equivalent to the one above
this->member = 5;
}
void static StaticFunction()
{
//We cannot access member variables
//in a static member function
//The following line will give a compile error
member = 5;
//This will give an error too
this->member = 5;
}
};
A static member function for the window procedure would be useful if
we could just get our hands on a pointer to the window class object
(our Step 1: Set up the Thread Local Storage to store our CWinApp::CWinApp(HINSTANCE hInstance) : m_hInstance(hInstance)
{
if (GetApp() == 0)
{
st_dwTlsIndex = ::TlsAlloc();
//snip
}
}
Step 2: Store our // Ensure this thread has the TLS index set TLSData* pTLSData = GetApp()->SetTlsIndex(); // Store the CWnd pointer in thread local storage pTLSData->pCWnd = this; Step 3: Extract the pointer from Thread Local Storage and add it to the STL map during the initial creation of the window: // Retrieve the pointer to the TLS Data
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
// Retrieve pointer to CWnd object from Thread Local Storage TLS
w = pTLSData->pCWnd;
// Store the CWnd pointer in the HWND map
GetApp()->AddToMap(hWnd, w);
return w->WndProc(hWnd, uMsg, wParam, lParam);
Step 4: For each subsequent window message, we extract the
pointer from the STL map and use it to redirect the message handling to
the appropriate CWnd* w = GetApp()->GetCWndFromMap(hWnd); return w->WndProc(hWnd, uMsg, wParam, lParam);
Window creation in detailNow that we've had a look at the window procedure it is time to see how these fit together as we create the window. This is the code which creates the window: HWND CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hParent,
HMENU hMenu, LPVOID lpParam /*= NULL*/)
{
try
{
// Test if Win32++ has been started
if (0 == GetApp())
throw CWinException(_T("Win32++ has not been initialised properly.\n
Start the Win32++ by inheriting from CWinApp."));
// Only one window per CWnd instance allowed
if (::IsWindow(m_hWnd))
throw CWinException(_T("CWnd::CreateEx ... Window already exists"));
// Ensure a window class is registered
TCHAR ClassName[MAX_STRING_SIZE] = _T("");
if (0 == lstrlen(lpszClassName) )
lstrcpyn (ClassName, _T("Win32++ Window"), MAX_STRING_SIZE);
else
// Create our own local copy of szClassName.
lstrcpyn(ClassName, lpszClassName, MAX_STRING_SIZE);
WNDCLASS wc = {0};
wc.lpszClassName = ClassName;
wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
if (!RegisterClass(wc)) // Register the window class (if not already registered)
throw CWinException(_T("CWnd::CreateEx Failed to register window class"));
// Ensure this thread has the TLS index set
TLSData* pTLSData = GetApp()->SetTlsIndex();
// Store the CWnd pointer in thread local storage
pTLSData->pCWnd = this;
// Create window
m_hWnd = ::CreateWindowEx(dwExStyle, ClassName, lpszWindowName, dwStyle, x, y, nWidth,
nHeight, hParent, hMenu, GetApp()->GetInstanceHandle(), lpParam);
// Now handle window creation failure
if (!m_hWnd)
throw CWinException(_T("CWnd::CreateEx ... Failed to Create Window"));
m_hWndParent = hParent;
// Automatically subclass predefined window class types
::GetClassInfo(GetApp()->GetInstanceHandle(), lpszClassName, &wc);
if (wc.lpfnWndProc != GetApp()->m_Callback)
{
Subclass();
// Send a message to force the HWND to be added to the map
::SendMessage(m_hWnd, WM_NULL, 0, 0);
OnCreate(); // We missed the WM_CREATE message, so call OnCreate now
}
// Clear the CWnd pointer from TLS
pTLSData->pCWnd = NULL;
// Window creation is complete. Now call OnInitialUpdate
OnInitialUpdate();
}
catch (const CWinException &e)
{
e.MessageBox();
}
return m_hWnd;
} // HWND CWnd::CreateEx()
The next code segment is the window procedure which first receives
the messages.
We extract the pointer to the LRESULT CALLBACK CWnd::StaticWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
try
{
CWnd* w = GetApp()->GetCWndFromMap(hWnd);
if (0 != w)
{
// CWnd pointer found, so call the CWnd's WndProc
return w->WndProc(hWnd, uMsg, wParam, lParam);
}
else
{
// The CWnd pointer wasn't found in the map, so add it now
// Retrieve the pointer to the TLS Data
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
if (NULL == pTLSData)
throw CWinException(_T("CWnd::StaticCBTProc ... Unable to get TLS"));
// Retrieve pointer to CWnd object from Thread Local Storage TLS
w = pTLSData->pCWnd;
if (NULL == w)
throw CWinException(_T("CWnd::StaticWindowProc .. Failed to route message"));
pTLSData->pCWnd = NULL;
// Store the CWnd pointer in the HWND map
GetApp()->AddToMap(hWnd, w);
// Store the HWND in the CWnd object early
w->m_hWnd = hWnd;
return w->WndProc(hWnd, uMsg, wParam, lParam);
}
}
//snip
Finally, the next code segment shows the function called by LRESULT CWnd::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Override this function in your class derrived from CWnd to handle
// window messages. A typical function might look like this:
// switch (uMsg)
// {
// case MESSAGE1: // Some Windows API message
// OnMessage1(); // A user defined function
// break; // Also do default processing
// case MESSAGE2:
// OnMessage2();
// return x; // Don't do default processing, but instead return
// // a value recommended by the Windows API documentation
// }
// Always pass unhandled messages on to WndProcDefault
return WndProcDefault(hWnd, uMsg, wParam, lParam);
}
History
ConclusionWith technique we are able to forward every window message to the appropriate CWnd object. No messages are discarded, even during window creation. This technique also supports all window types, including dialogs, common controls, MDI frames, and property sheets. |