分析过CWindow 和CWindowImpl之后,已经了解了他们的构成。但是程序运行中创建一个窗口的具体过程仍需弄清楚。
一个典型的win32程序,创建一个窗口的过程为:
1. 注册窗口
2. 创建窗口
3. 建立一个这个窗口的 WndProc
注册窗口的目的是让操作系统知道它,然后就可以通过窗口的名称去建立窗口,操作系统就知道该窗口的相关信息。建立窗口的时候,还需要为这个窗口指定它自己的窗口函数 WndProc. 让窗口知道如何接收和处理自己的消息。
注册窗口
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTWIN32));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_TESTWIN32);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
创建窗口
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
窗口函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
#define DECLARE_WND_CLASS(WndClassName) \
static ATL::CWndClassInfo& GetWndClassInfo() \
{ \
static ATL::CWndClassInfo wc = \
{ \
{ sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, \
0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, \
NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
}; \
return wc; \
}
struct _ATL_WNDCLASSINFOW
{
WNDCLASSEXW m_wc;
LPCWSTR m_lpszOrigName;
WNDPROC pWndProc;
LPCWSTR m_lpszCursorID;
BOOL m_bSystemCursor;
ATOM m_atom;
WCHAR m_szAutoName[5+sizeof(void*)*CHAR_BIT];
ATOM Register(_In_ WNDPROC* p)
{
return AtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule, this, p);
}
};
第一个成员就是 WNDCLASSEXW m_wc; 是
WNDCLASSEX 的UNICODE版本。
CWindowImpl 类里面的Create函数:
HWND Create(
_In_opt_ HWND hWndParent,
_In_ _U_RECT rect = NULL,
_In_opt_z_ LPCTSTR szWindowName = NULL,
_In_ DWORD dwStyle = 0,
_In_ DWORD dwExStyle = 0,
_In_ _U_MENUorID MenuOrID = 0U,
_In_opt_ LPVOID lpCreateParam = NULL)
{
if (T::GetWndClassInfo().m_lpszOrigName == NULL)
T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);
dwStyle = T::GetWndStyle(dwStyle);
dwExStyle = T::GetWndExStyle(dwExStyle);
// set caption
if (szWindowName == NULL)
szWindowName = T::GetWndCaption();
return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,
dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
}
};
需要注意的是
ATOM Register(_In_ WNDPROC* p)
它是将 m_pfnSuperWindowProc 的指针带进去,如果该类有是从其他窗口派生的,那么这个成员会返回并保存基类的窗口函数。
我们来看看另外一个宏: BEGIN_MSG_MAP, 它实际上定义了一个叫做 BOOL ProcessWindowMessage的函数。而这个函数得根在 CMessageMap 类里面,在哪里它被定义成纯虚函数。这个先说到这里,放下来。
现在我们的目标是找窗口函数 winproc,注册的时候,它使用了 m_pfnSuperWindowProc, 而它是CWindwoImpl的基类 CWindowImplBaseT的成员变量,是一个窗口函数指针。初始化它的时候,是这样的。
CWindowImplBaseT() : m_pfnSuperWindowProc(::DefWindowProc)
{
}
现在,窗口已经建立好了,并且这个函数的窗口函数也已经注册:StartWindowProc,一旦有消息需要处理,就会调用这个窗口函数。
正常情况下,这样处理就已经完了。窗口能够正确运行。但ATL 在这个地方来了个奇妙的变招。你会问了,我自己的窗口函数呢,我自己的消息什么时候处理呢? 这个变招解决了这个问题。
template
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(
_In_ HWND hWnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData();
ATLASSERT(pThis != NULL);
if(!pThis)
{
return 0;
}
pThis->m_hWnd = hWnd;
// Initialize the thunk. This is allocated in CWindowImplBaseT::Create,
// so failure is unexpected here.
pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
#ifdef _DEBUG
// check if somebody has subclassed us already since we discard it
if(pOldProc != StartWindowProc)
ATLTRACE(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.\n"));
#else
(pOldProc); // avoid unused warning
#endif
return pProc(hWnd, uMsg, wParam, lParam);
}
看看这个地方,来了个变身,这个函数一但执行,窗口的winproc就被改变了。并且转向用另一个窗口函数 pThis->GetWindowProc()。这样一来,实际的窗口函数就变成了WindowProc,这也是一个静态函数。
pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
这两句改变内容比较复杂, 其目的就是用This指针,代替了函数调用时候的hWnd参数。在下面的函数里面,在此将它转换回This指针。因为静态函数式没有this指针的。
template
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(
_In_ HWND hWnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
// set a ptr to this message and save the old value
_ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam);
const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg;
pThis->m_pCurrentMsg = &msg;
// pass to the message map to process
LRESULT lRes = 0;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg);
// do the default processing if message was not handled
if(!bRet)
{
if(uMsg != WM_NCDESTROY)
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
else
{
// unsubclass, if needed
LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)
::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);
// mark window as destryed
pThis->m_dwState |= WINSTATE_DESTROYED;
}
}
if((pThis->m_dwState & WINSTATE_DESTROYED) && pOldMsg== NULL)
{
// clear out window handle
HWND hWndThis = pThis->m_hWnd;
pThis->m_hWnd = NULL;
pThis->m_dwState &= ~WINSTATE_DESTROYED;
// clean up after window is destroyed
pThis->m_pCurrentMsg = pOldMsg;
pThis->OnFinalMessage(hWndThis);
}else {
pThis->m_pCurrentMsg = pOldMsg;
}
return lRes;
}