Windows GUI采用基于事件驅(qū)動(dòng)的編程模型,事實(shí)上幾乎所有的界面庫都是這樣做的。在純粹的Window32 SDK編程時(shí)代,人們還可以搞懂整個(gè)Windows窗體創(chuàng)建和消息的流通過程,但是在現(xiàn)在各種框架的包裝下很多在Window32 SDK下很明顯易懂的東西顯得不是那么簡單了。本文力圖去繁求簡,教你看懂所有框架的基本構(gòu)造,而事實(shí)上對(duì)于了解這一切的人來說,這些界面框架的設(shè)計(jì)都是如出一轍的,希望看完本文,再去看常見的MFC/WTL等框架時(shí),不會(huì)再覺得有任何的不適。
C程序的處理辦法
1.基本原理
先說古老的Win32 SDK的做法,他們很明顯,這里還是先貼上代碼,為了縮減篇幅很多地方我就省略了
[cpp] view plaincopy print?
int ?WINAPI?WinMain?(HINSTANCE ?hInstance,?HINSTANCE ?hPrevInstance,?PSTR ?szCmdLine,?int ?iCmdShow)??{?? ????static ?TCHAR ?szAppName[]?=?TEXT?("TestClass" );?? ????HWND ?????????hwnd;?? ????MSG??????????msg;?? ????WNDCLASSEX???wndclassex?=?{0};?? ?? ?????? ????wndclassex.cbSize????????=?sizeof (WNDCLASSEX);?? ????wndclassex.style?????????=?CS_HREDRAW?|?CS_VREDRAW;?? ????wndclassex.lpfnWndProc???=?WndProc?...?? ?????? ?????? ????if ?(!RegisterClassEx?(&wndclassex))?? ????{?? ????????MessageBox?(NULL,?TEXT?("RegisterClassEx?failed!" ),?szAppName,?MB_ICONERROR);?? ????????return ?0;?? ????}?? ?? ?????? ????hwnd?=?CreateWindowEx?(WS_EX_OVERLAPPEDWINDOW,??? ??????????????????????????szAppName,??? ??????????????????????????...?? ?????? ?????? ????ShowWindow?(hwnd,?iCmdShow);?? ????UpdateWindow?(hwnd);?? ?????? ?????? ????while ?(GetMessage?(&msg,?NULL,?0,?0))?? ????{?? ????????TranslateMessage?(&msg);?? ????????DispatchMessage?(&msg);?? ????}?? ????return ?msg.wParam;?? }?? ?? ?? LRESULT ?CALLBACK?WndProc?(HWND ?hwnd,?UINT ?message,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ????HDC ?hdc;?? ????PAINTSTRUCT?ps;?? ?? ?????? ????switch ?(message)?? ????{?? ????case ?WM_CREATE:?? ????????return ?(0);?? ?????????? ????case ?WM_PAINT:?? ????????...?? ????????return ?(0);?? ?????????? ????case ?WM_DESTROY:?? ????????PostQuitMessage?(0);?? ????????return ?(0);?? ????}?? ?? ?????? ????return ?DefWindowProc?(hwnd,?message,?wParam,?lParam);?? }??
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{static TCHAR szAppName[] = TEXT ("TestClass");HWND hwnd;MSG msg;WNDCLASSEX wndclassex = {0};//1.設(shè)計(jì)窗口類wndclassex.cbSize = sizeof(WNDCLASSEX);wndclassex.style = CS_HREDRAW | CS_VREDRAW;wndclassex.lpfnWndProc = WndProc ...//2.注冊(cè)窗口類if (!RegisterClassEx (&wndclassex)){MessageBox (NULL, TEXT ("RegisterClassEx failed!"), szAppName, MB_ICONERROR);return 0;}//3.創(chuàng)建窗口hwnd = CreateWindowEx (WS_EX_OVERLAPPEDWINDOW, szAppName, ...//4.顯示窗口ShowWindow (hwnd, iCmdShow);UpdateWindow (hwnd);//5.開始消息循環(huán),又稱消息泵while (GetMessage (&msg, NULL, 0, 0)){TranslateMessage (&msg);DispatchMessage (&msg);}return msg.wParam;
}//回調(diào)函數(shù)中做消息分發(fā)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{HDC hdc;PAINTSTRUCT ps;//分發(fā)switch (message){case WM_CREATE:return (0);case WM_PAINT:...return (0);case WM_DESTROY:PostQuitMessage (0);return (0);}//默認(rèn)處理函數(shù)return DefWindowProc (hwnd, message, wParam, lParam);
}
設(shè)計(jì)窗口類和注冊(cè)窗口類可稱為
InitApplication ,即初始化Windows 應(yīng)用所需要做的工作,這個(gè)窗口類
可以是公用的 。
創(chuàng)建一個(gè)窗口和顯示可稱為InitInstance ,即初始化一個(gè)Windows 應(yīng)用實(shí)例所需要做的工作,對(duì)每個(gè)窗體來說這都是唯一的 ,可做定制化修改。
開啟消息泵可稱為Run ,一單消息泵開啟,意味著一個(gè)程序開始接受消息和分發(fā)消息,整個(gè)應(yīng)用程序算是開始運(yùn)行了。
在WndProc中做的是判斷對(duì)應(yīng)的消息,然后做對(duì)應(yīng)的處理工作。
2.改進(jìn)窗口創(chuàng)建
可以看到,最原始的Win32 SDK編程完全是面向過程編程創(chuàng)建,比較繁瑣,為了簡化編寫,可在VS2008里面打開新建一個(gè)Win32 程序可以看到代碼如下:
[cpp] view plaincopy print?
?? ...?? MyRegisterClass(hInstance);?? ?? ?? if ?(!InitInstance?(hInstance,?nCmdShow))??{?? ????return ?FALSE;?? }?? ?? hAccelTable?=?LoadAccelerators(hInstance,?MAKEINTRESOURCE(IDC_WIN321));?? ?? ?? while ?(GetMessage(&msg,?NULL,?0,?0))??{?? ????if ?(!TranslateAccelerator(msg.hwnd,?hAccelTable,?&msg))?? ????{?? ????????TranslateMessage(&msg);?? ????????DispatchMessage(&msg);?? ????}?? }?? ?? return ?(int )?msg.wParam;??
// 1.設(shè)計(jì)和注冊(cè)消息類...MyRegisterClass(hInstance);// 2.執(zhí)行應(yīng)用程序初始化:if (!InitInstance (hInstance, nCmdShow)){return FALSE;}hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN321));// 3.主消息循環(huán):while (GetMessage(&msg, NULL, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}return (int) msg.wParam;
可以看到按照在基本原理中講的,這里微軟的做法也一樣,
按照三大部分封裝到函數(shù)中 ,簡化操作,InitApplication命名成了MyRegisterClass而已。
3.改進(jìn)消息分發(fā)
前面講了改進(jìn)窗口創(chuàng)建,但是消息分發(fā)仍然是一團(tuán)亂麻,所有的消息響應(yīng)都塞在switch case中,這里我們自然想到和窗口創(chuàng)建一樣,對(duì)應(yīng)的處理分發(fā)到函數(shù)中。而事實(shí)上微軟也確實(shí)是這么做的,微軟提供了頭文件WindowsX.h來幫助我們分發(fā)消息,具體如下:
[cpp] view plaincopy print?
LRESULT ?CALLBACK?WndProc(HWND ?hWnd,?UINT ?message,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ????switch ?(message)?? ????{?? ????????HANDLE_MSG(hWnd,?WM_PAINT,?Cls_OnPaint);?? ????????HANDLE_MSG(hWnd,?WM_DESTROY,?Cls_OnDestroy);?? ????}?? ?? ????return ?DefWindowProc(hWnd,?message,?wParam,?lParam);?? }?? ?? void ?Cls_OnPaint(HWND ?hwnd)??{?? ????HDC ?hdc;?? ????PAINTSTRUCT?ps;?? ?? ????hdc?=?BeginPaint(hwnd,?&ps);?? ?????? ????EndPaint(hwnd,?&ps);?? }?? ?? void ?Cls_OnDestroy(HWND ?hwnd)??{?? ????PostQuitMessage(0);?? }??
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){HANDLE_MSG(hWnd, WM_PAINT, Cls_OnPaint);HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);}return DefWindowProc(hWnd, message, wParam, lParam);
}void Cls_OnPaint(HWND hwnd)
{HDC hdc;PAINTSTRUCT ps;hdc = BeginPaint(hwnd, &ps);//...EndPaint(hwnd, &ps);
}void Cls_OnDestroy(HWND hwnd)
{PostQuitMessage(0);
}可以看到,這里借助于HANDLE_MSG宏讓消息對(duì)應(yīng)到具體的處理函數(shù)上,HANDLE_MSG展開如下:
[cpp] view plaincopy print?
#define?HANDLE_MSG(hwnd,?message,?fn)????\ ??????case ?(message):?return ?HANDLE_##message((hwnd),?(wParam),?(lParam),?(fn))??
#define HANDLE_MSG(hwnd, message, fn) \case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))HANDLE##message為處理函數(shù)
可看到這里
借助宏來減少switch case代碼的編寫量 ,但實(shí)際代碼內(nèi)容是一樣的。
實(shí)際上對(duì)話框的處理略有不同,如下:
[cpp] view plaincopy print?
#define?chHANDLE_DLGMSG(hwnd,?message,?fn)?case?(message):?\ ??????return ?(SetDlgMsgResult(hwnd,?uMsg,?HANDLE_##message((hwnd),?(wParam),?(lParam),?(fn))))?? ?? INT_PTR ?CALLBACK?Dlg_Proc(HWND ?hDlg,?UINT ?message,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ????UNREFERENCED_PARAMETER(lParam);?? ????switch ?(message)?? ????{?? ????????chHANDLE_DLGMSG(hwnd,?WM_INITDIALOG,?Dlg_OnInitDialog);?? ????????chHANDLE_DLGMSG(hwnd,?WM_COMMAND,????Dlg_OnCommand);?? ????}?? ?? ????return ?(INT_PTR )FALSE;?? }??
#define chHANDLE_DLGMSG(hwnd, message, fn) case (message): \return (SetDlgMsgResult(hwnd, uMsg, HANDLE_##message((hwnd), (wParam), (lParam), (fn))))INT_PTR CALLBACK Dlg_Proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{UNREFERENCED_PARAMETER(lParam);switch (message){chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);}return (INT_PTR)FALSE;
}這里的chHANDLE_DLGMSG是仿照HANDLE_MSG自定義的。
C++程序的處理辦法
在C++時(shí)代,人們提倡面向?qū)ο缶幊?#xff0c;對(duì)于窗口的創(chuàng)建和消息的分發(fā)響應(yīng)都是窗口的行為,所以幾乎所有的框架都是想辦法把這兩者封裝在一起 ,這也是我們講解的重點(diǎn)。對(duì)于C++程序我們先講大框架,再講窗口類封裝。
1.MFC大框架
盜用侯捷先生一張圖,MFC的基本層次結(jié)構(gòu)如下:
MFC將開啟消息循環(huán)放到CWinThread中,將窗口注冊(cè)、創(chuàng)建、消息分發(fā)響應(yīng)均放到CWnd中處理,這樣所有和窗口處理相關(guān)的都是由同一個(gè)類來完成 ,符合C++的封裝特性,也便于使用。
VS安裝完目錄VC\atlmfc\src\mfc下有部分mfc源碼,我們直接看微軟的實(shí)現(xiàn)。
首先,入口文件appmodul.cpp中定義入口如下:
[cpp] view plaincopy print?
extern ?"C" ?int ?WINAPI?_tWinMain(HINSTANCE ?hInstance,?HINSTANCE ?hPrevInstance,?_In_?LPTSTR ?lpCmdLine,?int ?nCmdShow)??#pragma?warning(suppress:?4985) ??{?? ?????? ????return ?AfxWinMain(hInstance,?hPrevInstance,?lpCmdLine,?nCmdShow);?? }??
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)
#pragma warning(suppress: 4985)
{// call shared/exported WinMainreturn AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
然后,在winmain.cpp查看定義AfxWinMain如下
[cpp] view plaincopy print?
int ?AFXAPI?AfxWinMain(HINSTANCE ?hInstance,?HINSTANCE ?hPrevInstance,?_In_?LPTSTR ?lpCmdLine,?int ?nCmdShow)??{?? ????...?? ?? ?????? ????if ?(!AfxWinInit(hInstance,?hPrevInstance,?lpCmdLine,?nCmdShow))?? ????????goto ?InitFailure;?? ?? ?????? ????if ?(pApp?!=?NULL?&&?!pApp->InitApplication())?? ????????goto ?InitFailure;?? ?? ?????? ????if ?(!pThread->InitInstance())?? ????{?? ????????...?? ????}?? ????nReturnCode?=?pThread->Run();?? ?? ????...?? }??
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)
{...// AFX internal initializationif (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))goto InitFailure;// App global initializations (rare)if (pApp != NULL && !pApp->InitApplication())goto InitFailure;// Perform specific initializationsif (!pThread->InitInstance()){...}nReturnCode = pThread->Run();...
}
所以還是InitApplication、InitInstance、Run三大塊,AfxWinInit用于做一些框架的初始化工作。
CWinApp::InitApplication在appcore.cpp中,和C程序略有不同,這里的工作主要是Doc模板管理器的初始化工作。
CThread::InitInstance虛函數(shù)會(huì)被用戶改寫,在這當(dāng)中調(diào)用CWnd完成窗口的注冊(cè)和創(chuàng)建,這個(gè)在之后一起講
CThread::Run在thrdcore.cpp中,Run-》PumpMessage-》AfxInternalPumpMessage完成消息泵的開啟,如下:
[cpp] view plaincopy print?
BOOL ?AFXAPI?AfxInternalPumpMessage()??{?? ????_AFX_THREAD_STATE?*pState?=?AfxGetThreadState();?? ?? ????if ?(!::GetMessage(&(pState->m_msgCur),?NULL,?NULL,?NULL))?? ????{?? ...?? ????????return ?FALSE;?? ????}?? ?? ...?? ?? ????if ?(pState->m_msgCur.message?!=?WM_KICKIDLE?&&?!AfxPreTranslateMessage(&(pState->m_msgCur)))?? ????{?? ????????::TranslateMessage(&(pState->m_msgCur));?? ????????::DispatchMessage(&(pState->m_msgCur));?? ????}?? ??return ?TRUE;?? }??
BOOL AFXAPI AfxInternalPumpMessage()
{_AFX_THREAD_STATE *pState = AfxGetThreadState();if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)){
...return FALSE;}...if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur))){::TranslateMessage(&(pState->m_msgCur));::DispatchMessage(&(pState->m_msgCur));}return TRUE;
}
2.MFC封裝窗口創(chuàng)建和消息分發(fā)
利用C++面向?qū)ο蟮奶卣?#xff0c;將窗口創(chuàng)建和消息分發(fā)、響應(yīng)分裝在一個(gè)類中,這樣一個(gè)窗口類對(duì)應(yīng)一個(gè)實(shí)際窗口 ,非常簡單直觀。
首先我們思考下,把窗口創(chuàng)建和消息分發(fā)封裝在一起有哪些難點(diǎn)?
1.怎么將不同的窗口過程勾到一起
歷史經(jīng)驗(yàn)告訴我們,專制往往有時(shí)候好辦事。如果每個(gè)窗口都有自己的窗口過程,那樣處理起來就比較麻煩,最好的做法是所有的窗口在同一個(gè)窗口過程中控制分發(fā)。
2.同一窗口過程中怎樣將不同的hwnd消息分發(fā)給對(duì)應(yīng)的CWnd類去處理響應(yīng)
因?yàn)榇翱诨卣{(diào)函數(shù)的限制,回調(diào)函數(shù)不能擁有對(duì)應(yīng)CWnd類的this指針,也就是說來了窗口消息,怎樣才能辨別對(duì)應(yīng)的hwnd對(duì)應(yīng)的CWnd,把消息分發(fā)給CWnd去處理呢?
3.最后,如果CWnd拿到了消息,怎樣去簡單有效的去處理和響應(yīng)呢
我們說過消息的響應(yīng)也是在Cwnd中處理,怎樣將拿到的消息對(duì)應(yīng)成具體的類成員函數(shù)呢?
這些問題串通后,MFC的做法,我們畫一張消息流通圖如下:
a).窗口創(chuàng)建
同樣我們拿源碼來解釋,
在MFC中我們自定義的窗口類繼承關(guān)系如下:
CWnd->CFrameWnd->CMyFrameWnd
winfrm.cpp中CFrameWnd::LoadFrame
首先,調(diào)用GetIconWndClass->AfxRegisterWndClass完成窗口類設(shè)計(jì)和注冊(cè) ,
然后,調(diào)用CFrameWnd::Create->CWnd::CreateEx完成窗口創(chuàng)建,如下:
[cpp] view plaincopy print?
BOOL ?CFrameWnd::LoadFrame(UINT ?nIDResource,?DWORD ?dwDefaultStyle,?CWnd*?pParentWnd,?CCreateContext*?pContext)??{?? ...?? ????LPCTSTR ?lpszClass?=?GetIconWndClass(dwDefaultStyle,?nIDResource);?? ????CString?strTitle?=?m_strTitle;?? ????if ?(!Create(lpszClass,?strTitle,?dwDefaultStyle,?rectDefault,?pParentWnd,?ATL_MAKEINTRESOURCE(nIDResource),?0L,?pContext))?? ????{?? ????????return ?FALSE;????? ????}?? ?? ...?? }??
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
{
...LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);CString strTitle = m_strTitle;if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault, pParentWnd, ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext)){return FALSE; // will self destruct on failure normally}...
}
[cpp] view plaincopy print?
LPCTSTR ?CFrameWnd::GetIconWndClass(DWORD ?dwDefaultStyle,?UINT ?nIDResource)??{?? ...?? ????if ?(hIcon?!=?NULL)?? ????{?? ...?? ????????{?? ?????????????? ????????????return ?AfxRegisterWndClass(wndcls.style,?wndcls.hCursor,?wndcls.hbrBackground,?hIcon);?? ????????}?? ????}?? ????return ?NULL;?????????? }??
LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)
{
...if (hIcon != NULL){
...{// register a very similar WNDCLASSreturn AfxRegisterWndClass(wndcls.style, wndcls.hCursor, wndcls.hbrBackground, hIcon);}}return NULL; // just use the default
}
在wincore.cpp的CWnd::CreateEx中,
創(chuàng)建窗口
[cpp] view plaincopy print?
BOOL ?CWnd::CreateEx(...)??{?? ...?? ?????? ????CREATESTRUCT?cs;?? ????cs.dwExStyle?=?dwExStyle;?? ????cs.lpszClass?=?lpszClassName;?? ????cs.lpszName?=?lpszWindowName;?? ????cs.style?=?dwStyle;?? ????cs.x?=?x;?? ????cs.y?=?y;?? ????cs.cx?=?nWidth;?? ????cs.cy?=?nHeight;?? ????cs.hwndParent?=?hWndParent;?? ????cs.hMenu?=?nIDorHMenu;?? ????cs.hInstance?=?AfxGetInstanceHandle();?? ????cs.lpCreateParams?=?lpParam;?? ?? ????...?? ?? ????AfxHookWindowCreate(this );?? ????HWND ?hWnd?=?::AfxCtxCreateWindowEx(cs.dwExStyle,?cs.lpszClass,?? ????????????cs.lpszName,?cs.style,?cs.x,?cs.y,?cs.cx,?cs.cy,?? ????????????cs.hwndParent,?cs.hMenu,?cs.hInstance,?cs.lpCreateParams);?? ?? ????...?? }??
BOOL CWnd::CreateEx(...)
{
...// allow modification of several common create parametersCREATESTRUCT cs;cs.dwExStyle = dwExStyle;cs.lpszClass = lpszClassName;cs.lpszName = lpszWindowName;cs.style = dwStyle;cs.x = x;cs.y = y;cs.cx = nWidth;cs.cy = nHeight;cs.hwndParent = hWndParent;cs.hMenu = nIDorHMenu;cs.hInstance = AfxGetInstanceHandle();cs.lpCreateParams = lpParam;...AfxHookWindowCreate(this);HWND hWnd = ::AfxCtxCreateWindowEx(cs.dwExStyle, cs.lpszClass,cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);...
}其中,在
AfxHookWindowCreate中
安裝鉤子使所有窗口消息勾到一起處理 如下:
[cpp] view plaincopy print?
void ?AFXAPI?AfxHookWindowCreate(CWnd*?pWnd)??{?? ...?? ????????pThreadState->m_hHookOldCbtFilter?=?::SetWindowsHookEx(WH_CBT,?_AfxCbtFilterHook,?NULL,?::GetCurrentThreadId());?? ????...?? }??
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
...pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());...
}在_AfxCbtFilterHook中代碼如下:
[cpp] view plaincopy print?
LRESULT ?CALLBACK?_AfxCbtFilterHook(int ?code,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ...?? ????????if ?(pWndInit?!=?NULL)?? ????????{?? ????????????AFX_MANAGE_STATE(pWndInit->m_pModuleState);?? ?? ?????????????? ????????????ASSERT(CWnd::FromHandlePermanent(hWnd)?==?NULL);?? ?? ?????????????? ????????????pWndInit->Attach(hWnd);?? ?????????????? ????????????pWndInit->PreSubclassWindow();?? ?? ????????????WNDPROC?*pOldWndProc?=?pWndInit->GetSuperWndProcAddr();?? ????????????ASSERT(pOldWndProc?!=?NULL);?? ?? ?????????????? ????????????WNDPROC?afxWndProc?=?AfxGetAfxWndProc();?? ????????????oldWndProc?=?(WNDPROC)SetWindowLongPtr(hWnd,?GWLP_WNDPROC,?? ????????????????(DWORD_PTR )afxWndProc);?? ????????????ASSERT(oldWndProc?!=?NULL);?? ????????????if ?(oldWndProc?!=?afxWndProc)?? ????????????????*pOldWndProc?=?oldWndProc;?? ?? ????????...?? }??
LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
...if (pWndInit != NULL){AFX_MANAGE_STATE(pWndInit->m_pModuleState);// the window should not be in the permanent map at this timeASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);// connect the HWND to pWndInit...pWndInit->Attach(hWnd);// allow other subclassing to occur firstpWndInit->PreSubclassWindow();WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();ASSERT(pOldWndProc != NULL);// subclass the window with standard AfxWndProcWNDPROC afxWndProc = AfxGetAfxWndProc();oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);ASSERT(oldWndProc != NULL);if (oldWndProc != afxWndProc)*pOldWndProc = oldWndProc;...
}其中pWndInit->Attach完成
句柄hwnd和窗口類CWnd*的綁定 ,建立一張hash表,對(duì)應(yīng)的HashMap結(jié)構(gòu)可參照CWnd::afxMapHWND對(duì)應(yīng)的winhand_.h中的CHandleMap
SetWindowLongPtr使所有的窗口響應(yīng)都走AfxWndProc中 ,在AfxWndProc中完成消息分發(fā)到對(duì)應(yīng)的Cwnd中。
b).消息的分發(fā)和響應(yīng)
[cpp] view plaincopy print?
LRESULT ?CALLBACK?AfxWndProc(HWND ?hWnd,?UINT ?nMsg,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ...?? ?????? ????CWnd*?pWnd?=?CWnd::FromHandlePermanent(hWnd);?? ????...?? ????return ?AfxCallWndProc(pWnd,?hWnd,?nMsg,?wParam,?lParam);?? }??
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
...// all other messages route through message mapCWnd* pWnd = CWnd::FromHandlePermanent(hWnd);...return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}可以看到,
根據(jù)hwnd取得對(duì)應(yīng)的CWnd* ,然后看AfxCallWndProc如下:
[cpp] view plaincopy print?
LRESULT ?AFXAPI?AfxCallWndProc(CWnd*?pWnd,?HWND ?hWnd,?UINT ?nMsg,?WPARAM ?wParam?=?0,?LPARAM ?lParam?=?0)??{?? ...?? ?? ?????????? ????????lResult?=?pWnd->WindowProc(nMsg,?wParam,?lParam);?? ...?? }??
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)
{
...// delegate to object's WindowProclResult = pWnd->WindowProc(nMsg, wParam, lParam);
...
}
在這里開始
調(diào)用CWnd成員響應(yīng)函數(shù) ,終于又回到CWnd中了,接著往下看
[cpp] view plaincopy print?
LRESULT ?CWnd::WindowProc(UINT ?message,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ...?? ????if ?(!OnWndMsg(message,?wParam,?lParam,?&lResult))?? ????????...?? }??
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
...if (!OnWndMsg(message, wParam, lParam, &lResult))...
}
在OnWndMsg中做了什么呢?看下面代碼
[cpp] view plaincopy print?
BOOL ?CWnd::OnWndMsg(UINT ?message,?WPARAM ?wParam,?LPARAM ?lParam,?LRESULT *?pResult)??{?? ...?? ?????? ????if ?(message?==?WM_COMMAND)?? ????{?? ????????if ?(OnCommand(wParam,?lParam))?? ????????{?? ????????????lResult?=?1;?? ????????????goto ?LReturnTrue;?? ????????}?? ????????return ?FALSE;?? ????}?? ...?? ?? ?????? ????const ?AFX_MSGMAP*?pMessageMap;?pMessageMap?=?GetMessageMap();?? ????...?? ?? ????????for ?(;?pMessageMap->pfnGetBaseMap?!=?NULL;?? ????????????pMessageMap?=?(*pMessageMap->pfnGetBaseMap)())?? ????????{?? ????????????...?? ????????}?? ?? ...?? }??
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
...//WM_COMMAND特殊處理if (message == WM_COMMAND){if (OnCommand(wParam, lParam)){lResult = 1;goto LReturnTrue;}return FALSE;}
...//找到當(dāng)前的CWnd類的MessageMap表,查表得到對(duì)應(yīng)響應(yīng)函數(shù)并處理const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();...for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;pMessageMap = (*pMessageMap->pfnGetBaseMap)()){...}...
}
可以看到,到此
完成了CWnd中的查表調(diào)用消息對(duì)應(yīng)的處理函數(shù) ,至于具體的OnCommand消息處理和具體響應(yīng)函數(shù)調(diào)用過程,恕不詳述。
但是等等,還有一個(gè)問題沒有解決,那就是CWnd中的消息-處理函數(shù)表怎么來的 ,這就是我們常見的如下結(jié)構(gòu)
[cpp] view plaincopy print?
BEGIN_MESSAGE_MAP(CMainFrame,?...)?? ????ON_WM_CREATE()?? ????ON_WM_SETFOCUS()?? ...?? END_MESSAGE_MAP()??
BEGIN_MESSAGE_MAP(CMainFrame, ...)ON_WM_CREATE()ON_WM_SETFOCUS()
...
END_MESSAGE_MAP()頭文件中的DECLARE_MESSAGE_MAP定義如下,可以看到回調(diào)函數(shù)中
取消息映射表的函數(shù)GetMessageMap在此定義
[cpp] view plaincopy print?
#define?DECLARE_MESSAGE_MAP()?\ ??protected :?\??????static ?const ?AFX_MSGMAP*?PASCAL?GetThisMessageMap();?\?? ????virtual ?const ?AFX_MSGMAP*?GetMessageMap()?const ;?\??
#define DECLARE_MESSAGE_MAP() \
protected: \static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \virtual const AFX_MSGMAP* GetMessageMap() const; \
BEGIN_MESSAGE_MAP結(jié)構(gòu)展開如下
[cpp] view plaincopy print?
#define?BEGIN_MESSAGE_MAP(theClass,?baseClass)?\ ??????PTM_WARNING_DISABLE?\?? ????const ?AFX_MSGMAP*?theClass::GetMessageMap()?const ?\?? ????????{?return ?GetThisMessageMap();?}?\?? ????const ?AFX_MSGMAP*?PASCAL?theClass::GetThisMessageMap()?\?? ????{?\?? ????????typedef ?theClass?ThisClass;????????????????????????\?? ????????typedef ?baseClass?TheBaseClass;????????????????????\?? ????????static ?const ?AFX_MSGMAP_ENTRY?_messageEntries[]?=??\?? ????????{??
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \PTM_WARNING_DISABLE \const AFX_MSGMAP* theClass::GetMessageMap() const \{ return GetThisMessageMap(); } \const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \{ \typedef theClass ThisClass; \typedef baseClass TheBaseClass; \static const AFX_MSGMAP_ENTRY _messageEntries[] = \{可見
真正的映射表結(jié)構(gòu)_massgeEntries在此定義 ,ON_WM_CRATE完成實(shí)際的表內(nèi)容填充,例如:
[cpp] view plaincopy print?
#define?ON_WM_CREATE()?\ ??????{?WM_CREATE,?0,?0,?0,?AfxSig_is,?\?? ????????(AFX_PMSG)?(AFX_PMSGW)?\?? ????????(static_cast <?int ?(AFX_MSG_CALL?CWnd::*)(LPCREATESTRUCT)?>?(?&ThisClass?::?OnCreate))?},??
#define ON_WM_CREATE() \{ WM_CREATE, 0, 0, 0, AfxSig_is, \(AFX_PMSG) (AFX_PMSGW) \(static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },將
WM_CREATE和OnCreate函數(shù)綁定
至此,窗口類的封裝過程盡在眼前,可能你覺得過程比較繁瑣,那么我把它概括如下:
1.Create窗口時(shí)完成兩件事:(1)窗口過程勾到一起處理(2)hwnd和對(duì)應(yīng)的CWnd*綁定
2.CWnd中利用BEGIN_MESSAGE_MAP結(jié)構(gòu)定義【消息-響應(yīng)函數(shù)】的路由表
3.響應(yīng)函數(shù)中根據(jù)傳入的hwnd查表得到CWnd*,調(diào)用CWnd->GetMassageMap獲取【消息-響應(yīng)函數(shù)】表,查對(duì)應(yīng)消息的響應(yīng)函數(shù),調(diào)用完成響應(yīng) 現(xiàn)在再返回去看,是不是清晰明朗了?
3.ATL大框架
MFC出現(xiàn)在C++尚未完善時(shí),沒有采用c++的高級(jí)特性,基本上都是繼承和虛函數(shù)、查表,類的層次過多,顯得比較臃腫。相比而言,ATL就好多了,采用模板技術(shù)簡化了設(shè)計(jì),也沒有那么多的層次結(jié)構(gòu),非常輕量,在此基礎(chǔ)上上封裝的WTL界面庫被越來越多的人使用。WTL雖然是在ATL上封裝的,但是窗口的創(chuàng)建和消息分發(fā)原理并沒有變,所以我們?nèi)匀灰訟TL來講解整個(gè)過程。
ATL的框架基本上是自己搭建起來的,自己編寫_tWinMain函數(shù),期間可借助CMessageLoop完成消息泵的開啟,如下:
[cpp] view plaincopy print?
int ?WINAPI?_tWinMain(HINSTANCE ?hInstance,?HINSTANCE ?,?LPTSTR ?lpstrCmdLine,?int ?nCmdShow)??{?? ...?? ????int ?nRet?=?Run(lpstrCmdLine,?nCmdShow);?? ...?? }?? ?? ?? int ?Run(LPTSTR ??=?NULL,?int ?nCmdShow?=?SW_SHOWDEFAULT)??{?? ????CMessageLoop?theLoop;?? ????_Module.AddMessageLoop(&theLoop);?? ?? ????CMainFrame?wndMain;?? ?? ????if (wndMain.CreateEx()?==?NULL)?? ????{?? ????????ATLTRACE(_T("Main?window?creation?failed!\n" ));?? ????????return ?0;?? ????}?? ?? ????wndMain.ShowWindow(nCmdShow);?? ?? ????int ?nRet?=?theLoop.Run();?? ?? ????_Module.RemoveMessageLoop();?? ????return ?nRet;?? }??
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
...int nRet = Run(lpstrCmdLine, nCmdShow);
...
}int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)
{CMessageLoop theLoop;_Module.AddMessageLoop(&theLoop);CMainFrame wndMain;if(wndMain.CreateEx() == NULL){ATLTRACE(_T("Main window creation failed!\n"));return 0;}wndMain.ShowWindow(nCmdShow);int nRet = theLoop.Run();_Module.RemoveMessageLoop();return nRet;
}
可知CMainFrame::CreateEx完成窗口創(chuàng)建,atlapp.h中CMessageLoop完成消息泵開啟,代碼如下:
[cpp] view plaincopy print?
?? ????int ?Run()?? ????{?? ...?? ?? ????????for (;;)?? ????????{?? ...?? ?? ????????????bRet?=?::GetMessage(&m_msg,?NULL,?0,?0);?? ...?? ????????????if (!PreTranslateMessage(&m_msg))?? ????????????{?? ????????????????::TranslateMessage(&m_msg);?? ????????????????::DispatchMessage(&m_msg);?? ????????????}?? ...?? ????????}?? ????????return ?(int )m_msg.wParam;?? ????}??
// message loopint Run(){
...for(;;){
...bRet = ::GetMessage(&m_msg, NULL, 0, 0);
...if(!PreTranslateMessage(&m_msg)){::TranslateMessage(&m_msg);::DispatchMessage(&m_msg);}
...}return (int)m_msg.wParam;}
整個(gè)大框架和Win32 SDK很像,沒什么封裝,唯一不同的是所有的窗口創(chuàng)建和消息分發(fā)都封裝到窗口類中了,這個(gè)接下來重點(diǎn)說說。
4.ATL封裝窗口創(chuàng)建和消息分發(fā) 和MFC封裝窗口類一樣,這里同樣需要考慮之前說的三個(gè)問題,重要的事情說三遍,我就再貼一次之前的話。
1.怎么將不同的窗口過程勾到一起
2.同一窗口過程中怎樣將不同的hwnd消息分發(fā)給對(duì)應(yīng)的CWnd類去處理響應(yīng)
3.最后,如果CWnd拿到了消息,怎樣去簡單有效的去處理和響應(yīng)呢
這里和MFC一樣,
1.所有的窗體窗口過程函數(shù)一樣,保證統(tǒng)一處理
2.hwnd和對(duì)應(yīng)窗口類是通過匯編強(qiáng)制粘連起來的
3.CWnd拿到消息后類似前面的C語言通過一組宏簡化switch case結(jié)構(gòu)調(diào)用對(duì)應(yīng)的消息響應(yīng)函數(shù)
同樣我們從源碼開始入手:
所有的窗體類都繼承于CWndImpl,我們關(guān)注這個(gè)類即可
a).窗口創(chuàng)建
atlwin.app中CWindowImpl::Create中如下,取得窗口信息,注冊(cè)窗口類
[cpp] view plaincopy print?
HWND ?Create(HWND ?hWndParent,?_U_RECT?rect?=?NULL,?LPCTSTR ?szWindowName?=?NULL,??????????????DWORD ?dwStyle?=?0,?DWORD ?dwExStyle?=?0,?? ????????????_U_MENUorID?MenuOrID?=?0U,?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);?? ?? ????...?? ?? ????return ?CWindowImplBaseT<?TBase,?TWinTraits?>::Create(hWndParent,?rect,?szWindowName,?? ?????????????????????????????????????????????????????????dwStyle,?dwExStyle,?MenuOrID,?atom,?lpCreateParam);?? }??
HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,DWORD dwStyle = 0, DWORD dwExStyle = 0,_U_MENUorID MenuOrID = 0U, 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);...return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
}
可以看到調(diào)用
GetWndClassInfo.Register注冊(cè)窗口類 ,每個(gè)類中使用DECLARE_WND_CLASS等宏來填充對(duì)應(yīng)信息。
DECLARE_WND_CLASS展開如下:
[cpp] view plaincopy print?
#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;?\?? }??
#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; \
}可知默認(rèn)的
所有窗口的窗口過程函數(shù)是StartWindowProc,完成統(tǒng)一控制
實(shí)際的
窗口創(chuàng)建函數(shù) 如下:
[cpp] view plaincopy print?
template ?<class ?TBase,?class ?TWinTraits>??HWND ?CWindowImplBaseT<?TBase,?TWinTraits?>::Create(...)??{?? ????BOOL ?result;?? ????ATLASSUME(m_hWnd?==?NULL);?? ?? ?????? ????result?=?m_thunk.Init(NULL,NULL);?? ...?? ?? ?????? ????_AtlWinModule.AddCreateWndData(&m_thunk.cd,?this );?? ?? ?????? ????HWND ?hWnd?=?::CreateWindowEx(dwExStyle,?MAKEINTATOM(atom),?szWindowName,?? ????????dwStyle,?rect.m_lpRect->left,?rect.m_lpRect->top,?rect.m_lpRect->right?-?rect.m_lpRect->left,?? ????????rect.m_lpRect->bottom?-?rect.m_lpRect->top,?hWndParent,?MenuOrID.m_hMenu,?? ????????_AtlBaseModule.GetModuleInstance(),?lpCreateParam);?? ...?? }??
template <class TBase, class TWinTraits>
HWND CWindowImplBaseT< TBase, TWinTraits >::Create(...)
{BOOL result;ATLASSUME(m_hWnd == NULL);// 初始化Thunk結(jié)構(gòu)體result = m_thunk.Init(NULL,NULL);
...//保存當(dāng)前窗口類指針到全局_AtlWinModule.AddCreateWndData(&m_thunk.cd, this);//創(chuàng)建窗口HWND hWnd = ::CreateWindowEx(dwExStyle, MAKEINTATOM(atom), szWindowName,dwStyle, rect.m_lpRect->left, rect.m_lpRect->top, rect.m_lpRect->right - rect.m_lpRect->left,rect.m_lpRect->bottom - rect.m_lpRect->top, hWndParent, MenuOrID.m_hMenu,_AtlBaseModule.GetModuleInstance(), lpCreateParam);
...
}
這里的Thunk和保存指針到全局之后再說。
至此創(chuàng)建過程完成。
b).消息分發(fā)和響應(yīng)
前面說了,所有的窗口類的響應(yīng)函數(shù)都是在StartWndProc中,如下:
[cpp] view plaincopy print?
template ?<class ?TBase,?class ?TWinTraits>??LRESULT ?CALLBACK?CWindowImplBaseT<?TBase,?TWinTraits?>::StartWindowProc(HWND ?hWnd,?UINT ?uMsg,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ????CWindowImplBaseT<?TBase,?TWinTraits?>*?pThis?=?(CWindowImplBaseT<?TBase,?TWinTraits?>*)_AtlWinModule.ExtractCreateWndData();?? ????...?? ????pThis->m_thunk.Init(pThis->GetWindowProc(),?pThis);?? ????WNDPROC?pProc?=?pThis->m_thunk.GetWNDPROC();?? ????WNDPROC?pOldProc?=?(WNDPROC)::SetWindowLongPtr(hWnd,?GWLP_WNDPROC,?(LONG_PTR )pProc);?? ????...?? ????return ?pProc(hWnd,?uMsg,?wParam,?lParam);?? }??
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData();...pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);WNDPROC pProc = pThis->m_thunk.GetWNDPROC();WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);...return pProc(hWnd, uMsg, wParam, lParam);
}可知,
第一次窗口響應(yīng)會(huì)進(jìn)入到此函數(shù) ,這里的代碼從全局結(jié)構(gòu)中拿到當(dāng)前窗口類的指針,初始化Thunk,設(shè)置Thunk為代理窗口響應(yīng)函數(shù),通過pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
將窗口的this指針和窗口消息處理函數(shù)WindowProc初始化到thunk靜態(tài)結(jié)構(gòu)里。設(shè)置所有的窗體過程函數(shù)為WindowProc。
這里用到了Thunk轉(zhuǎn)換技術(shù),所謂Thunk就是轉(zhuǎn)換的意思,這里的基本思想是替換掉傳統(tǒng)的WndProc的第一個(gè)句柄參數(shù)hwnd,讓這里的hwnd實(shí)際上是對(duì)應(yīng)的CWndImpl的指針,這樣完成了hwnd到窗體類的映射。 具體的實(shí)現(xiàn)在atlstdthunk.h中,如下:
[cpp] view plaincopy print?
#pragma?pack(push,1) ??struct ?_stdcallthunk??{?? ????DWORD ???m_mov;???????????? ????DWORD ???m_this;??????????? ????BYTE ????m_jmp;???????????? ????DWORD ???m_relproc;???????? ????BOOL ?Init(DWORD_PTR ?proc,?void *?pThis)?? ????{?? ????????m_mov?=?0x042444C7;???? ????????m_this?=?PtrToUlong(pThis);?? ????????m_jmp?=?0xe9;?? ????????m_relproc?=?DWORD ((INT_PTR )proc?-?((INT_PTR )this +sizeof (_stdcallthunk)));????? ????????FlushInstructionCache(GetCurrentProcess(),?this ,?sizeof (_stdcallthunk));?????? ????????return ?TRUE;?? ????}?? ...?? };?? #pragma?pack(pop) ??
#pragma pack(push,1)
struct _stdcallthunk
{DWORD m_mov; // 替換hwnd參數(shù)為對(duì)應(yīng)CWndImpl指針 mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)DWORD m_this; //BYTE m_jmp; // 跳轉(zhuǎn)到WndProcDWORD m_relproc; // relative jmpBOOL Init(DWORD_PTR proc, void* pThis){m_mov = 0x042444C7; //C7 44 24 0Cm_this = PtrToUlong(pThis);m_jmp = 0xe9;m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk))); // write block from data cache andFlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk)); // flush from instruction cachereturn TRUE;}
...
};
#pragma pack(pop)
WindowProc處理如下:
[cpp] view plaincopy print?
template ?<class ?TBase,?class ?TWinTraits>??LRESULT ?CALLBACK?CWindowImplBaseT<?TBase,?TWinTraits?>::WindowProc(HWND ?hWnd,?UINT ?uMsg,?WPARAM ?wParam,?LPARAM ?lParam)??{?? ????CWindowImplBaseT<?TBase,?TWinTraits?>*?pThis?=?(CWindowImplBaseT<?TBase,?TWinTraits?>*)hWnd;?? ????...?? ????BOOL ?bRet?=?pThis->ProcessWindowMessage(pThis->m_hWnd,?uMsg,?wParam,?lParam,?lRes,?0);?? ????...?? }??
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;//hwnd轉(zhuǎn)換成CWindowImplBaseT指針...BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);//調(diào)用對(duì)應(yīng)的窗體類的ProcessWindowMessage處理函數(shù)...
}可知在具體的窗口過程函數(shù)中,
將hWnd轉(zhuǎn)換成對(duì)應(yīng)的窗口類,接著調(diào)用窗口類的ProcessWindowMessage 調(diào)用對(duì)應(yīng)的窗體類處理函數(shù)。
每個(gè)窗體類都有ProcessWindowMessage函數(shù),它使用一組宏定義如下:
[cpp] view plaincopy print?
BEGIN_MSG_MAP(CMainFrame)?? ????MESSAGE_HANDLER(WM_CREATE,?OnCreate)?? ????...?? END_MSG_MAP()??
BEGIN_MSG_MAP(CMainFrame)MESSAGE_HANDLER(WM_CREATE, OnCreate)...
END_MSG_MAP()
展開顯示如下:
[cpp] view plaincopy print?
#define?BEGIN_MSG_MAP(theClass)?\ ??public :?\??????BOOL ?ProcessWindowMessage(HWND ?hWnd,?UINT ?uMsg,?WPARAM ?wParam,?LPARAM ?lParam,?LRESULT &?lResult,?DWORD ?dwMsgMapID?=?0)?\?? ????{?\?? ????????BOOL ?bHandled?=?TRUE;?\?? ????????(hWnd);?\?? ????????(uMsg);?\?? ????????(wParam);?\?? ????????(lParam);?\?? ????????(lResult);?\?? ????????(bHandled);?\?? ????????switch (dwMsgMapID)?\?? ????????{?\?? ????????case ?0:?? ?????????? #define?MESSAGE_HANDLER(msg,?func)?\ ??????if (uMsg?==?msg)?\?? ????{?\?? ????????bHandled?=?TRUE;?\?? ????????lResult?=?func(uMsg,?wParam,?lParam,?bHandled);?\?? ????????if (bHandled)?\?? ????????????return ?TRUE;?\?? ????}??
#define BEGIN_MSG_MAP(theClass) \
public: \BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) \{ \BOOL bHandled = TRUE; \(hWnd); \(uMsg); \(wParam); \(lParam); \(lResult); \(bHandled); \switch(dwMsgMapID) \{ \case 0:#define MESSAGE_HANDLER(msg, func) \if(uMsg == msg) \{ \bHandled = TRUE; \lResult = func(uMsg, wParam, lParam, bHandled); \if(bHandled) \return TRUE; \}其實(shí)就是
宏定義的switch case結(jié)構(gòu) 。
至此整個(gè)過程如下:
1.Create中指定統(tǒng)一的窗口過程StartWindowProc
2.StartWindowProc第一次響應(yīng)時(shí)完成hwnd和CWndImpl的映射綁定,設(shè)置響應(yīng)函數(shù)為WindowProc
3.WindowProc中轉(zhuǎn)hwnd為CWndImpl*,調(diào)用對(duì)應(yīng)類的ProcessWindowMessage分發(fā)處理消息
4.BEGIN_MSG_MAP簡化switch case結(jié)構(gòu),在每個(gè)窗口類中分發(fā)處理
總之封裝窗口類需要考慮之前說的三點(diǎn),搞懂了這三點(diǎn)其他的問題也就迎刃而解了。最后不要嫌我煩,再貼一遍我一直強(qiáng)調(diào)的重點(diǎn),牢記這三點(diǎn),看相應(yīng)的框架封裝過程大同小異:
1.怎么將不同的窗口過程勾到一起
2.同一窗口過程中怎樣將不同的hwnd消息分發(fā)給對(duì)應(yīng)的CWnd類去處理響應(yīng)
3.最后,如果CWnd拿到了消息,怎樣去簡單有效的去處理和響應(yīng)呢
原創(chuàng),轉(zhuǎn)載請(qǐng)注明來自http://blog.csdn.net/wenzhou1219
總結(jié)
以上是生活随笔 為你收集整理的深入解析Windows窗口创建和消息分发 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。