zoukankan      html  css  js  c++  java
  • 深入解析Windows窗口创建和消息分发(三个核心问题:怎么将不同的窗口过程勾到一起,将不同的hwnd消息分发给对应的CWnd类去处理,CWnd如何简单有效的去处理消息,由浅入深,非常清楚) good

    笔记:争取不用看下面的内容,只看自己的笔记,就能记住这个流程,就算明白了:

    _tWinMain-->AfxWinMain,它调用四个函数:
    -->AfxWinInit用于做一些框架的初始化工作。
    -->CWinApp::InitApplication在appcore.cpp中,和C程序略有不同,这里的工作主要是Doc模板管理器的初始化工作。
    -->CThread::InitInstance虚函数会被用户改写,在这当中调用CWnd完成窗口的注册和创建,这个在之后一起讲
    -->CThread::Run在thrdcore.cpp中,Run->PumpMessage->AfxInternalPumpMessage完成消息泵的开启,如下:
    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;
    }

    1.怎么将不同的窗口过程勾到一起
    历史经验告诉我们,专制往往有时候好办事。如果每个窗口都有自己的窗口过程,那样处理起来就比较麻烦,最好的做法是所有的窗口在同一个窗口过程中控制分发。
    在BOOL CWnd::CreateEx(...)之中,执行AfxHookWindowCreate函数(使用SetWindowsHookEx API函数收集当前线程的所有消息),收集所有消息放到_AfxCbtFilterHook函数里。
    这个函数里执行:
    WNDPROC afxWndProc = AfxGetAfxWndProc();
    oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (DWORD_PTR)afxWndProc); // 注意afxWndProc函数

    2.同一窗口过程中怎样将不同的hwnd消息分发给对应的CWnd类去处理响应
    因为窗口回调函数的限制,回调函数不能拥有对应CWnd类的this指针,也就是说来了窗口消息,怎样才能辨别对应的hwnd对应的CWnd,把消息分发给CWnd去处理呢?
    LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
    {
    // all other messages route through message map
    CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
    return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); // 转换成 pWnd->WindowProc(nMsg, wParam, lParam);
    // 它就是CWnd::WindowProc,然后调用CWnd::OnWndMsg(message, wParam, lParam, &lResult)) 然后 AFX_MSGMAP* pMessageMap = GetMessageMap(); 向上查表取得消息函数
    }

    3.最后,如果CWnd拿到了消息,怎样去简单有效的去处理和响应呢
    我们说过消息的响应也是在Cwnd中处理,怎样将拿到的消息对应成具体的类成员函数呢?
    BEGIN_MESSAGE_MAP(CMainFrame, ...)
    ON_WM_CREATE()
    ON_WM_SETFOCUS()
    END_MESSAGE_MAP()

    #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函数绑定

    ATL就好多了,采用模板技术简化了设计,也没有那么多的层次结构,非常轻量

    -------------------------------------------------------------------------------------------------------------------

    Windows GUI采用基于事件驱动的编程模型,事实上几乎所有的界面库都是这样做的。在纯粹的Window32 SDK编程时代,人们还可以搞懂整个Windows窗体创建和消息的流通过程,但是在现在各种框架的包装下很多在Window32 SDK下很明显易懂的东西显得不是那么简单了。本文力图去繁求简,教你看懂所有框架的基本构造,而事实上对于了解这一切的人来说,这些界面框架的设计都是如出一辙的,希望看完本文,再去看常见的MFC/WTL等框架时,不会再觉得有任何的不适。

    C程序的处理办法

    1.基本原理

    先说古老的Win32 SDK的做法,他们很明显,这里还是先贴上代码,为了缩减篇幅很多地方我就省略了

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)  
    2. {  
    3.     static TCHAR szAppName[] = TEXT ("TestClass");  
    4.     HWND         hwnd;  
    5.     MSG          msg;  
    6.     WNDCLASSEX   wndclassex = {0};  
    7.   
    8.     //1.设计窗口类  
    9.     wndclassex.cbSize        = sizeof(WNDCLASSEX);  
    10.     wndclassex.style         = CS_HREDRAW | CS_VREDRAW;  
    11.     wndclassex.lpfnWndProc   = WndProc ...  
    12.       
    13.     //2.注册窗口类  
    14.     if (!RegisterClassEx (&wndclassex))  
    15.     {  
    16.         MessageBox (NULL, TEXT ("RegisterClassEx failed!"), szAppName, MB_ICONERROR);  
    17.         return 0;  
    18.     }  
    19.   
    20.     //3.创建窗口  
    21.     hwnd = CreateWindowEx (WS_EX_OVERLAPPEDWINDOW,   
    22.                           szAppName,   
    23.                           ...  
    24.       
    25.     //4.显示窗口  
    26.     ShowWindow (hwnd, iCmdShow);  
    27.     UpdateWindow (hwnd);  
    28.       
    29.     //5.开始消息循环,又称消息泵  
    30.     while (GetMessage (&msg, NULL, 0, 0))  
    31.     {  
    32.         TranslateMessage (&msg);  
    33.         DispatchMessage (&msg);  
    34.     }  
    35.     return msg.wParam;  
    36. }  
    37.   
    38. //回调函数中做消息分发  
    39. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
    40. {  
    41.     HDC hdc;  
    42.     PAINTSTRUCT ps;  
    43.   
    44.     //分发  
    45.     switch (message)  
    46.     {  
    47.     case WM_CREATE:  
    48.         return (0);  
    49.           
    50.     case WM_PAINT:  
    51.         ...  
    52.         return (0);  
    53.           
    54.     case WM_DESTROY:  
    55.         PostQuitMessage (0);  
    56.         return (0);  
    57.     }  
    58.   
    59.     //默认处理函数  
    60.     return DefWindowProc (hwnd, message, wParam, lParam);  
    61. }  

    设计窗口类和注册窗口类可称为InitApplication,即初始化Windows 应用所需要做的工作,这个窗口类可以是公用的。

    创建一个窗口和显示可称为InitInstance,即初始化一个Windows 应用实例所需要做的工作,对每个窗体来说这都是唯一的,可做定制化修改。

    开启消息泵可称为Run,一单消息泵开启,意味着一个程序开始接受消息和分发消息,整个应用程序算是开始运行了。

    在WndProc中做的是判断对应的消息,然后做对应的处理工作。

    2.改进窗口创建

    可以看到,最原始的Win32 SDK编程完全是面向过程编程创建,比较繁琐,为了简化编写,可在VS2008里面打开新建一个Win32 程序可以看到代码如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. // 1.设计和注册消息类  
    2. ...  
    3. MyRegisterClass(hInstance);  
    4.   
    5. // 2.执行应用程序初始化:  
    6. if (!InitInstance (hInstance, nCmdShow))  
    7. {  
    8.     return FALSE;  
    9. }  
    10.   
    11. hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN321));  
    12.   
    13. // 3.主消息循环:  
    14. while (GetMessage(&msg, NULL, 0, 0))  
    15. {  
    16.     if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))  
    17.     {  
    18.         TranslateMessage(&msg);  
    19.         DispatchMessage(&msg);  
    20.     }  
    21. }  
    22.   
    23. return (int) msg.wParam;  


    可以看到按照在基本原理中讲的,这里微软的做法也一样,按照三大部分封装到函数中,简化操作,InitApplication命名成了MyRegisterClass而已。

    3.改进消息分发

    前面讲了改进窗口创建,但是消息分发仍然是一团乱麻,所有的消息响应都塞在switch case中,这里我们自然想到和窗口创建一样,对应的处理分发到函数中。而事实上微软也确实是这么做的,微软提供了头文件WindowsX.h来帮助我们分发消息,具体如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
    2. {  
    3.     switch (message)  
    4.     {  
    5.         HANDLE_MSG(hWnd, WM_PAINT, Cls_OnPaint);  
    6.         HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);  
    7.     }  
    8.   
    9.     return DefWindowProc(hWnd, message, wParam, lParam);  
    10. }  
    11.   
    12. void Cls_OnPaint(HWND hwnd)  
    13. {  
    14.     HDC hdc;  
    15.     PAINTSTRUCT ps;  
    16.   
    17.     hdc = BeginPaint(hwnd, &ps);  
    18.     //...  
    19.     EndPaint(hwnd, &ps);  
    20. }  
    21.   
    22. void Cls_OnDestroy(HWND hwnd)  
    23. {  
    24.     PostQuitMessage(0);  
    25. }  

    可以看到,这里借助于HANDLE_MSG宏让消息对应到具体的处理函数上,HANDLE_MSG展开如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #define HANDLE_MSG(hwnd, message, fn)      
    2.     case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))  

    HANDLE##message为处理函数
    可看到这里借助宏来减少switch case代码的编写量,但实际代码内容是一样的。

    实际上对话框的处理略有不同,如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #define chHANDLE_DLGMSG(hwnd, message, fn) case (message):   
    2.     return (SetDlgMsgResult(hwnd, uMsg, HANDLE_##message((hwnd), (wParam), (lParam), (fn))))  
    3.   
    4. INT_PTR CALLBACK Dlg_Proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
    5. {  
    6.     UNREFERENCED_PARAMETER(lParam);  
    7.     switch (message)  
    8.     {  
    9.         chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);  
    10.         chHANDLE_DLGMSG(hwnd, WM_COMMAND,    Dlg_OnCommand);  
    11.     }  
    12.   
    13.     return (INT_PTR)FALSE;  
    14. }  

    这里的chHANDLE_DLGMSG是仿照HANDLE_MSG自定义的。

    C++程序的处理办法

    在C++时代,人们提倡面向对象编程,对于窗口的创建和消息的分发响应都是窗口的行为,所以几乎所有的框架都是想办法把这两者封装在一起,这也是我们讲解的重点。对于C++程序我们先讲大框架,再讲窗口类封装。

    1.MFC大框架

    盗用侯捷先生一张图,MFC的基本层次结构如下:

    MFC将开启消息循环放到CWinThread中,将窗口注册、创建、消息分发响应均放到CWnd中处理,这样所有和窗口处理相关的都是由同一个类来完成,符合C++的封装特性,也便于使用。

    VS安装完目录VCatlmfcsrcmfc下有部分mfc源码,我们直接看微软的实现。

    首先,入口文件appmodul.cpp中定义入口如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)  
    2. #pragma warning(suppress: 4985)  
    3. {  
    4.     // call shared/exported WinMain  
    5.     return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);  
    6. }  


    然后,在winmain.cpp查看定义AfxWinMain如下

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)  
    2. {  
    3.     ...  
    4.   
    5.     // AFX internal initialization  
    6.     if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))  
    7.         goto InitFailure;  
    8.   
    9.     // App global initializations (rare)  
    10.     if (pApp != NULL && !pApp->InitApplication())  
    11.         goto InitFailure;  
    12.   
    13.     // Perform specific initializations  
    14.     if (!pThread->InitInstance())  
    15.     {  
    16.         ...  
    17.     }  
    18.     nReturnCode = pThread->Run();  
    19.   
    20.     ...  
    21. }  


    所以还是InitApplication、InitInstance、Run三大块,AfxWinInit用于做一些框架的初始化工作。

    CWinApp::InitApplication在appcore.cpp中,和C程序略有不同,这里的工作主要是Doc模板管理器的初始化工作。

    CThread::InitInstance虚函数会被用户改写,在这当中调用CWnd完成窗口的注册和创建,这个在之后一起讲

    CThread::Run在thrdcore.cpp中,Run-》PumpMessage-》AfxInternalPumpMessage完成消息泵的开启,如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. BOOL AFXAPI AfxInternalPumpMessage()  
    2. {  
    3.     _AFX_THREAD_STATE *pState = AfxGetThreadState();  
    4.   
    5.     if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))  
    6.     {  
    7. ...  
    8.         return FALSE;  
    9.     }  
    10.   
    11. ...  
    12.   
    13.     if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))  
    14.     {  
    15.         ::TranslateMessage(&(pState->m_msgCur));  
    16.         ::DispatchMessage(&(pState->m_msgCur));  
    17.     }  
    18.   return TRUE;  
    19. }  

    2.MFC封装窗口创建和消息分发

    利用C++面向对象的特征,将窗口创建和消息分发、响应分装在一个类中,这样一个窗口类对应一个实际窗口,非常简单直观。

    首先我们思考下,把窗口创建和消息分发封装在一起有哪些难点?

    1.怎么将不同的窗口过程勾到一起

    历史经验告诉我们,专制往往有时候好办事。如果每个窗口都有自己的窗口过程,那样处理起来就比较麻烦,最好的做法是所有的窗口在同一个窗口过程中控制分发。

    2.同一窗口过程中怎样将不同的hwnd消息分发给对应的CWnd类去处理响应

    因为窗口回调函数的限制,回调函数不能拥有对应CWnd类的this指针,也就是说来了窗口消息,怎样才能辨别对应的hwnd对应的CWnd,把消息分发给CWnd去处理呢?

    3.最后,如果CWnd拿到了消息,怎样去简单有效的去处理和响应呢

    我们说过消息的响应也是在Cwnd中处理,怎样将拿到的消息对应成具体的类成员函数呢?

    这些问题串通后,MFC的做法,我们画一张消息流通图如下:

    a).窗口创建

    同样我们拿源码来解释,

     

    在MFC中我们自定义的窗口类继承关系如下:

    CWnd->CFrameWnd->CMyFrameWnd

    winfrm.cpp中CFrameWnd::LoadFrame

    首先,调用GetIconWndClass->AfxRegisterWndClass完成窗口类设计和注册

    然后,调用CFrameWnd::Create->CWnd::CreateEx完成窗口创建,如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)  
    2. {  
    3. ...  
    4.     LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);  
    5.     CString strTitle = m_strTitle;  
    6.     if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault, pParentWnd, ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext))  
    7.     {  
    8.         return FALSE;   // will self destruct on failure normally  
    9.     }  
    10.   
    11. ...  
    12. }  
    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)  
    2. {  
    3. ...  
    4.     if (hIcon != NULL)  
    5.     {  
    6. ...  
    7.         {  
    8.             // register a very similar WNDCLASS  
    9.             return AfxRegisterWndClass(wndcls.style, wndcls.hCursor, wndcls.hbrBackground, hIcon);  
    10.         }  
    11.     }  
    12.     return NULL;        // just use the default  
    13. }  


    在wincore.cpp的CWnd::CreateEx中,创建窗口

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. BOOL CWnd::CreateEx(...)  
    2. {  
    3. ...  
    4.     // allow modification of several common create parameters  
    5.     CREATESTRUCT cs;  
    6.     cs.dwExStyle = dwExStyle;  
    7.     cs.lpszClass = lpszClassName;  
    8.     cs.lpszName = lpszWindowName;  
    9.     cs.style = dwStyle;  
    10.     cs.x = x;  
    11.     cs.y = y;  
    12.     cs.cx = nWidth;  
    13.     cs.cy = nHeight;  
    14.     cs.hwndParent = hWndParent;  
    15.     cs.hMenu = nIDorHMenu;  
    16.     cs.hInstance = AfxGetInstanceHandle();  
    17.     cs.lpCreateParams = lpParam;  
    18.   
    19.     ...  
    20.   
    21.     AfxHookWindowCreate(this);  
    22.     HWND hWnd = ::AfxCtxCreateWindowEx(cs.dwExStyle, cs.lpszClass,  
    23.             cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,  
    24.             cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);  
    25.   
    26.     ...  
    27. }  

    其中,在AfxHookWindowCreate中安装钩子使所有窗口消息勾到一起处理如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. void AFXAPI AfxHookWindowCreate(CWnd* pWnd)  
    2. {  
    3. ...  
    4.         pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());  
    5.     ...  
    6. }  

    在_AfxCbtFilterHook中代码如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)  
    2. {  
    3. ...  
    4.         if (pWndInit != NULL)  
    5.         {  
    6.             AFX_MANAGE_STATE(pWndInit->m_pModuleState);  
    7.   
    8.             // the window should not be in the permanent map at this time  
    9.             ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);  
    10.   
    11.             // connect the HWND to pWndInit...  
    12.             pWndInit->Attach(hWnd);  
    13.             // allow other subclassing to occur first  
    14.             pWndInit->PreSubclassWindow();  
    15.   
    16.             WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();  
    17.             ASSERT(pOldWndProc != NULL);  
    18.   
    19.             // subclass the window with standard AfxWndProc  
    20.             WNDPROC afxWndProc = AfxGetAfxWndProc();  
    21.             oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,  
    22.                 (DWORD_PTR)afxWndProc);  
    23.             ASSERT(oldWndProc != NULL);  
    24.             if (oldWndProc != afxWndProc)  
    25.                 *pOldWndProc = oldWndProc;  
    26.   
    27.         ...  
    28. }  

    其中pWndInit->Attach完成句柄hwnd和窗口类CWnd*的绑定,建立一张hash表,对应的HashMap结构可参照CWnd::afxMapHWND对应的winhand_.h中的CHandleMap

    SetWindowLongPtr使所有的窗口响应都走AfxWndProc中,在AfxWndProc中完成消息分发到对应的Cwnd中。

    b).消息的分发和响应

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)  
    2. {  
    3. ...  
    4.     // all other messages route through message map  
    5.     CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);  
    6.     ...  
    7.     return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);  
    8. }  

    可以看到,根据hwnd取得对应的CWnd*,然后看AfxCallWndProc如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)  
    2. {  
    3. ...  
    4.   
    5.         // delegate to object's WindowProc  
    6.         lResult = pWnd->WindowProc(nMsg, wParam, lParam);  
    7. ...  
    8. }  


    在这里开始调用CWnd成员响应函数,终于又回到CWnd中了,接着往下看

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)  
    2. {  
    3. ...  
    4.     if (!OnWndMsg(message, wParam, lParam, &lResult))  
    5.         ...  
    6. }  


    在OnWndMsg中做了什么呢?看下面代码

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)  
    2. {  
    3. ...  
    4.     //WM_COMMAND特殊处理  
    5.     if (message == WM_COMMAND)  
    6.     {  
    7.         if (OnCommand(wParam, lParam))  
    8.         {  
    9.             lResult = 1;  
    10.             goto LReturnTrue;  
    11.         }  
    12.         return FALSE;  
    13.     }  
    14. ...  
    15.   
    16.     //找到当前的CWnd类的MessageMap表,查表得到对应响应函数并处理  
    17.     const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();  
    18.     ...  
    19.   
    20.         for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;  
    21.             pMessageMap = (*pMessageMap->pfnGetBaseMap)())  
    22.         {  
    23.             ...  
    24.         }  
    25.   
    26. ...  
    27. }  


    可以看到,到此完成了CWnd中的查表调用消息对应的处理函数,至于具体的OnCommand消息处理和具体响应函数调用过程,恕不详述。

    但是等等,还有一个问题没有解决,那就是CWnd中的消息-处理函数表怎么来的,这就是我们常见的如下结构

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. BEGIN_MESSAGE_MAP(CMainFrame, ...)  
    2.     ON_WM_CREATE()  
    3.     ON_WM_SETFOCUS()  
    4. ...  
    5. END_MESSAGE_MAP()  

    头文件中的DECLARE_MESSAGE_MAP定义如下,可以看到回调函数中取消息映射表的函数GetMessageMap在此定义

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #define DECLARE_MESSAGE_MAP()   
    2. protected:   
    3.     static const AFX_MSGMAP* PASCAL GetThisMessageMap();   
    4.     virtual const AFX_MSGMAP* GetMessageMap() const;   

    BEGIN_MESSAGE_MAP结构展开如下

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #define BEGIN_MESSAGE_MAP(theClass, baseClass)   
    2.     PTM_WARNING_DISABLE   
    3.     const AFX_MSGMAP* theClass::GetMessageMap() const   
    4.         { return GetThisMessageMap(); }   
    5.     const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap()   
    6.     {   
    7.         typedef theClass ThisClass;                          
    8.         typedef baseClass TheBaseClass;                      
    9.         static const AFX_MSGMAP_ENTRY _messageEntries[] =    
    10.         {  

    可见真正的映射表结构_massgeEntries在此定义,ON_WM_CRATE完成实际的表内容填充,例如:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #define ON_WM_CREATE()   
    2.     { WM_CREATE, 0, 0, 0, AfxSig_is,   
    3.         (AFX_PMSG) (AFX_PMSGW)   
    4.         (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },  

    WM_CREATE和OnCreate函数绑定

    至此,窗口类的封装过程尽在眼前,可能你觉得过程比较繁琐,那么我把它概括如下:

    1.Create窗口时完成两件事:(1)窗口过程勾到一起处理(2)hwnd和对应的CWnd*绑定

    2.CWnd中利用BEGIN_MESSAGE_MAP结构定义【消息-响应函数】的路由表

    3.响应函数中根据传入的hwnd查表得到CWnd*,调用CWnd->GetMassageMap获取【消息-响应函数】表,查对应消息的响应函数,调用完成响应

    现在再返回去看,是不是清晰明朗了?

    3.ATL大框架

    MFC出现在C++尚未完善时,没有采用c++的高级特性,基本上都是继承和虚函数、查表,类的层次过多,显得比较臃肿。相比而言,ATL就好多了,采用模板技术简化了设计,也没有那么多的层次结构,非常轻量,在此基础上上封装的WTL界面库被越来越多的人使用。WTL虽然是在ATL上封装的,但是窗口的创建和消息分发原理并没有变,所以我们仍然以ATL来讲解整个过程。

    ATL的框架基本上是自己搭建起来的,自己编写_tWinMain函数,期间可借助CMessageLoop完成消息泵的开启,如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)  
    2. {  
    3. ...  
    4.     int nRet = Run(lpstrCmdLine, nCmdShow);  
    5. ...  
    6. }  
    7.   
    8.   
    9. int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)  
    10. {  
    11.     CMessageLoop theLoop;  
    12.     _Module.AddMessageLoop(&theLoop);  
    13.   
    14.     CMainFrame wndMain;  
    15.   
    16.     if(wndMain.CreateEx() == NULL)  
    17.     {  
    18.         ATLTRACE(_T("Main window creation failed! "));  
    19.         return 0;  
    20.     }  
    21.   
    22.     wndMain.ShowWindow(nCmdShow);  
    23.   
    24.     int nRet = theLoop.Run();  
    25.   
    26.     _Module.RemoveMessageLoop();  
    27.     return nRet;  
    28. }  


    可知CMainFrame::CreateEx完成窗口创建,atlapp.h中CMessageLoop完成消息泵开启,代码如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. // message loop  
    2.     int Run()  
    3.     {  
    4. ...  
    5.   
    6.         for(;;)  
    7.         {  
    8. ...  
    9.   
    10.             bRet = ::GetMessage(&m_msg, NULL, 0, 0);  
    11. ...  
    12.             if(!PreTranslateMessage(&m_msg))  
    13.             {  
    14.                 ::TranslateMessage(&m_msg);  
    15.                 ::DispatchMessage(&m_msg);  
    16.             }  
    17. ...  
    18.         }  
    19.         return (int)m_msg.wParam;  
    20.     }  


    整个大框架和Win32 SDK很像,没什么封装,唯一不同的是所有的窗口创建和消息分发都封装到窗口类中了,这个接下来重点说说。

    4.ATL封装窗口创建和消息分发

    和MFC封装窗口类一样,这里同样需要考虑之前说的三个问题,重要的事情说三遍,我就再贴一次之前的话。

    1.怎么将不同的窗口过程勾到一起

    2.同一窗口过程中怎样将不同的hwnd消息分发给对应的CWnd类去处理响应

    3.最后,如果CWnd拿到了消息,怎样去简单有效的去处理和响应呢

    这里和MFC一样,

    1.所有的窗体窗口过程函数一样,保证统一处理

    2.hwnd和对应窗口类是通过汇编强制粘连起来的

    3.CWnd拿到消息后类似前面的C语言通过一组宏简化switch case结构调用对应的消息响应函数

    同样我们从源码开始入手:

    所有的窗体类都继承于CWndImpl,我们关注这个类即可

    a).窗口创建

    atlwin.app中CWindowImpl::Create中如下,取得窗口信息,注册窗口类

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,  
    2.             DWORD dwStyle = 0, DWORD dwExStyle = 0,  
    3.             _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)  
    4. {  
    5.     if (T::GetWndClassInfo().m_lpszOrigName == NULL)  
    6.         T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();  
    7.     ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);  
    8.   
    9.     dwStyle = T::GetWndStyle(dwStyle);  
    10.     dwExStyle = T::GetWndExStyle(dwExStyle);  
    11.   
    12.     ...  
    13.   
    14.     return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,  
    15.                                                          dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);  
    16. }  


    可以看到调用GetWndClassInfo.Register注册窗口类,每个类中使用DECLARE_WND_CLASS等宏来填充对应信息。

    DECLARE_WND_CLASS展开如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #define DECLARE_WND_CLASS(WndClassName)   
    2. static ATL::CWndClassInfo& GetWndClassInfo()   
    3. {   
    4.     static ATL::CWndClassInfo wc =   
    5.     {   
    6.         { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc,   
    7.           0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL },   
    8.         NULL, NULL, IDC_ARROW, TRUE, 0, _T("")   
    9.     };   
    10.     return wc;   
    11. }  

    可知默认的所有窗口的窗口过程函数是StartWindowProc,完成统一控制
    实际的窗口创建函数如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. template <class TBase, class TWinTraits>  
    2. HWND CWindowImplBaseT< TBase, TWinTraits >::Create(...)  
    3. {  
    4.     BOOL result;  
    5.     ATLASSUME(m_hWnd == NULL);  
    6.   
    7.     // 初始化Thunk结构体  
    8.     result = m_thunk.Init(NULL,NULL);  
    9. ...  
    10.   
    11.     //保存当前窗口类指针到全局  
    12.     _AtlWinModule.AddCreateWndData(&m_thunk.cd, this);  
    13.   
    14.     //创建窗口  
    15.     HWND hWnd = ::CreateWindowEx(dwExStyle, MAKEINTATOM(atom), szWindowName,  
    16.         dwStyle, rect.m_lpRect->left, rect.m_lpRect->top, rect.m_lpRect->right - rect.m_lpRect->left,  
    17.         rect.m_lpRect->bottom - rect.m_lpRect->top, hWndParent, MenuOrID.m_hMenu,  
    18.         _AtlBaseModule.GetModuleInstance(), lpCreateParam);  
    19. ...  
    20. }  


    这里的Thunk和保存指针到全局之后再说。

    至此创建过程完成。

    b).消息分发和响应

    前面说了,所有的窗口类的响应函数都是在StartWndProc中,如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. template <class TBase, class TWinTraits>  
    2. LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)  
    3. {  
    4.     CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData();  
    5.     ...  
    6.     pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);  
    7.     WNDPROC pProc = pThis->m_thunk.GetWNDPROC();  
    8.     WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);  
    9.     ...  
    10.     return pProc(hWnd, uMsg, wParam, lParam);  
    11. }  

    可知,第一次窗口响应会进入到此函数,这里的代码从全局结构中拿到当前窗口类的指针,初始化Thunk,设置Thunk为代理窗口响应函数,通过pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);将窗口的this指针和窗口消息处理函数WindowProc初始化到thunk静态结构里。设置所有的窗体过程函数为WindowProc。

    这里用到了Thunk转换技术,所谓Thunk就是转换的意思,这里的基本思想是替换掉传统的WndProc的第一个句柄参数hwnd,让这里的hwnd实际上是对应的CWndImpl的指针,这样完成了hwnd到窗体类的映射。具体的实现在atlstdthunk.h中,如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #pragma pack(push,1)  
    2. struct _stdcallthunk  
    3. {  
    4.     DWORD   m_mov;          // 替换hwnd参数为对应CWndImpl指针 mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)  
    5.     DWORD   m_this;         //  
    6.     BYTE    m_jmp;          // 跳转到WndProc  
    7.     DWORD   m_relproc;      // relative jmp  
    8.     BOOL Init(DWORD_PTR proc, void* pThis)  
    9.     {  
    10.         m_mov = 0x042444C7;  //C7 44 24 0C  
    11.         m_this = PtrToUlong(pThis);  
    12.         m_jmp = 0xe9;  
    13.         m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));   // write block from data cache and  
    14.         FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));    //  flush from instruction cache  
    15.         return TRUE;  
    16.     }  
    17. ...  
    18. };  
    19. #pragma pack(pop)  

    WindowProc处理如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. template <class TBase, class TWinTraits>  
    2. LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)  
    3. {  
    4.     CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;//hwnd转换成CWindowImplBaseT指针  
    5.     ...  
    6.     BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);//调用对应的窗体类的ProcessWindowMessage处理函数  
    7.     ...  
    8. }  

    可知在具体的窗口过程函数中,将hWnd转换成对应的窗口类,接着调用窗口类的ProcessWindowMessage调用对应的窗体类处理函数。
    每个窗体类都有ProcessWindowMessage函数,它使用一组宏定义如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. BEGIN_MSG_MAP(CMainFrame)  
    2.     MESSAGE_HANDLER(WM_CREATE, OnCreate)  
    3.     ...  
    4. END_MSG_MAP()  


    展开显示如下:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #define BEGIN_MSG_MAP(theClass)   
    2. public:   
    3.     BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0)   
    4.     {   
    5.         BOOL bHandled = TRUE;   
    6.         (hWnd);   
    7.         (uMsg);   
    8.         (wParam);   
    9.         (lParam);   
    10.         (lResult);   
    11.         (bHandled);   
    12.         switch(dwMsgMapID)   
    13.         {   
    14.         case 0:  
    15.           
    16. #define MESSAGE_HANDLER(msg, func)   
    17.     if(uMsg == msg)   
    18.     {   
    19.         bHandled = TRUE;   
    20.         lResult = func(uMsg, wParam, lParam, bHandled);   
    21.         if(bHandled)   
    22.             return TRUE;   
    23.     }  

    其实就是宏定义的switch case结构

    至此整个过程如下:

    1.Create中指定统一的窗口过程StartWindowProc

    2.StartWindowProc第一次响应时完成hwnd和CWndImpl的映射绑定,设置响应函数为WindowProc

    3.WindowProc中转hwnd为CWndImpl*,调用对应类的ProcessWindowMessage分发处理消息

    4.BEGIN_MSG_MAP简化switch case结构,在每个窗口类中分发处理


    总之封装窗口类需要考虑之前说的三点,搞懂了这三点其他的问题也就迎刃而解了。最后不要嫌我烦,再贴一遍我一直强调的重点,牢记这三点,看相应的框架封装过程大同小异:

    1.怎么将不同的窗口过程勾到一起

    2.同一窗口过程中怎样将不同的hwnd消息分发给对应的CWnd类去处理响应

    3.最后,如果CWnd拿到了消息,怎样去简单有效的去处理和响应呢

    原创,转载请注明来自http://blog.csdn.net/wenzhou1219

    http://blog.csdn.net/wenzhou1219/article/details/51173241

  • 相关阅读:
    [arc067F]Yakiniku Restaurants[矩阵差分]
    [2016北京集训测试赛3]masodik-[凸包]
    [WC2010][BZOJ1758]重建计划-[二分+分数规划+点分治]
    [2016北京集训测试赛7]isn-[树状数组+dp+容斥]
    [BZOJ1565][NOI2009]植物大战僵尸-[网络流-最小割+最大点权闭合子图+拓扑排序]
    [2016北京集训试题7]thr-[树形dp+树链剖分+启发式合并]
    [2016北京集训测试赛1]奇怪的树-[树链剖分]
    [2016北京集训测试赛1]兔子的字符串-[后缀数组+二分]
    模拟 [Sdoi2010]猪国杀
    DP 小奇挖矿2
  • 原文地址:https://www.cnblogs.com/findumars/p/6329614.html
Copyright © 2011-2022 走看看