程序的输出是一个窗口中的一条“Hello world from Drive”消息。在我们使用派生类之前,可以说一切都是顺利的。当我们从ZWindow派生出多于一个类的时候,问题就会发生。这样,所有的消息就都会流向ZWindow最后继承的那个派生类。让我们看看以下的程序。
程序71.
#include <windows.h>
class ZWindow;
ZWindow* g_pWnd = NULL;
class ZWindow
{
public:
HWND m_hWnd;
ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }
inline void Attach(HWND hWnd)
{ m_hWnd = hWnd; }
inline BOOL ShowWindow(int nCmdShow)
{ return ::ShowWindow(m_hWnd, nCmdShow); }
inline BOOL UpdateWindow()
{ return ::UpdateWindow(m_hWnd); }
inline HDC BeginPaint(LPPAINTSTRUCT ps)
{ return ::BeginPaint(m_hWnd, ps); }
inline BOOL EndPaint(LPPAINTSTRUCT ps)
{ return ::EndPaint(m_hWnd, ps); }
inline BOOL GetClientRect(LPRECT rect)
{ return ::GetClientRect(m_hWnd, rect); }
BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance,
HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW,
DWORD dwExStyle = 0, HMENU hMenu = 0, int x = CW_USEDEFAULT,
int y = CW_USEDEFAULT, int nWidth = CW_USEDEFAULT,
int nHeight = CW_USEDEFAULT)
{
m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle,
x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, NULL);
return m_hWnd != NULL;
}
virtual LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
RECT rect;
hDC = BeginPaint(&ps);
GetClientRect(&rect);
::DrawText(hDC, "Hello world", -1, &rect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(&ps);
return 0;
}
virtual LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
return 0;
}
virtual LRESULT OnCreate(WPARAM wParam, LPARAM lParam)
{
return 0;
}
virtual LRESULT OnKeyDown(WPARAM wParam, LPARAM lParam)
{
return 0;
}
static LRESULT CALLBACK StartWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
ZWindow* pThis = g_pWnd;
if (uMsg == WM_NCDESTROY)
::PostQuitMessage(0);
switch (uMsg)
{
case WM_CREATE:
pThis->OnCreate(wParam, lParam);
break;
case WM_PAINT:
pThis->OnPaint(wParam, lParam);
break;
case WM_LBUTTONDOWN:
pThis->OnLButtonDown(wParam, lParam);
break;
case WM_KEYDOWN:
pThis->OnKeyDown(wParam, lParam);
break;
case WM_DESTROY:
::PostQuitMessage(0);
break;
}
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
};
class ZDriveWindow1 : public ZWindow
{
public:
LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
RECT rect;
hDC = BeginPaint(&ps);
GetClientRect(&rect);
::SetBkMode(hDC, TRANSPARENT);
::DrawText(hDC, "ZDriveWindow1", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(&ps);
return 0;
}
LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
::MessageBox(NULL, "ZDriveWindow1::OnLButtonDown", "Msg", MB_OK);
return 0;
}
};
class ZDriveWindow2 : public ZWindow
{
public:
LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
RECT rect;
hDC = BeginPaint(&ps);
GetClientRect(&rect);
::SetBkMode(hDC, TRANSPARENT);
::Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);
::DrawText(hDC, "ZDriveWindow2", -1, &rect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(&ps);
return 0;
}
LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
::MessageBox(NULL, "ZDriveWindow2::OnLButtonDown", "Msg", MB_OK);
return 0;
}
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
char szAppName[] = "Hello world";
MSG msg;
WNDCLASS wnd;
ZDriveWindow1 zwnd1;
ZDriveWindow2 zwnd2;
wnd.cbClsExtra = NULL;
wnd.cbWndExtra = NULL;
wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
wnd.hCursor = LoadCursor(NULL, IDC_ARROW);
wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wnd.hInstance = hInstance;
wnd.lpfnWndProc = ZWindow::StartWndProc;
wnd.lpszClassName = szAppName;
wnd.lpszMenuName = NULL;
wnd.style = CS_HREDRAW | CS_VREDRAW;
if (!RegisterClass(&wnd))
{
MessageBox(NULL, "Can not register window class", "Error", MB_OK | MB_ICONINFORMATION);
return -1;
}
g_pWnd = &zwnd1;
zwnd1.Create(szAppName, "Hell world", hInstance);
zwnd1.ShowWindow(nCmdShow);
zwnd1.UpdateWindow();
g_pWnd = &zwnd2;
zwnd2.Create(szAppName, "Hello world", hInstance, zwnd1.m_hWnd, WS_VISIBLE | WS_CHILD | ES_MULTILINE, NULL, NULL, 0, 0, 150, 150);
while (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg);
}
return msg.wParam;
}
程序的输出表明,不管你单击了哪个窗口,都会弹出相同的MessageBox。

不管你单击了哪个窗口,你都会获得相同的消息框。这就意味着消息并没有传递给适当的窗口。事实上每个窗口都拥有自己的窗口过程,这些窗口过程处理窗口的所有消息。但是在这里,我们对第一个窗口使用了第二个窗口的回调函数,所以我们就不能对第一个窗口的消息进行处理了。
现在,我们最主要的问题是将窗口的回调函数和相应的窗口关联起来。这就意味着HWND应该和相应的派生类关联起来,所以消息应该发送给正确的窗口。解决这个问题可以有若干种方法,让我们来一个一个看一看。
首先我想出了一个最明显的解决方法,我们可以很容易地实现。方法是创建一个全局的结构,这个结构
存储HWND和相应的派生类。但是,这个方法有两个主要的问题。第一,这个结构会在窗口逐渐加入程序的过程中越变越大;第二,在结构变得很大之后,在这个结构中进行搜索肯定也会花费大笔时间。
而ATL的最主要目的就是使程序尽可能地小和快。并且,上述技术对于这两个标准都达不到。这个方法不单单是慢,还会在程序中包含大量窗口的情况下占用大量内存。
另一个可能的解决方案是使用WNDCLASS或WNDCLASSEX结构的cbWndExtra域。还有一个问题是,为什么不用cbClsExtra,而要用cbWndExtra呢?答案很简单,cbClsExtra为每个窗口类
存储额外的字节,而cbWndExtra为每个窗口存储额外的字节。并且,你可能会从一个窗口类创建多个窗口,这样,如果你使用了cbClsExtra的话,那么你就不能通过cbClsExtra区别不同的回调函数了,因为对于这些相同窗口类产生的窗口来说这个值是一样的。然后,将相应的派生类地址存储到cbWndExtra中。
这个和方法看起来比第一个要好,但是它仍然有两个问题。第一,如果用户希望使用cbWndExtra,那么他/她就可能会覆盖着一技术所使用的数据,这样客户就需要在使用cbWndExtra的时候十分注意了,以防丢失信息。那么好了,你可以在文