MFC

MFC HandlingWindowMessages

디버그정 2008. 9. 1. 15:14

MFC HandlingWindowMessages

FrontPage | RecentChanges | TitleIndex | UserPreferences | FindPage | Edit this page (last edited 2006-12-18)(diff)

Description

MFC에서 윈도우즈 메세지가 어떻게 전달되고 관리되는지에 대한 정보

MFC Handling Window Messaages

MFC에서는 일반적인 API의 메세지 루프와는 다른 방법으로 메세지를 관리하고 Dispatch를 한다. 먼저 기존의 WndProc?을 대체하여 관리하는 방법과 이와 관련해서 CCmdTarget? Class와 Message Map간에 매커니즘을 알아본다.

CCmdTarget Class와 Message Map

MFC에서 윈도우가 메세지를 받기위해서는 CCmdTarget?을 상속받아야 한다. CCmdTarget?를 상속받은 클래스는 Message Map을 사용 할 수 가 있다. 다음은 Message Map관련 구조체이다.
struct AFX_MSGMAP_ENTRY {
        UINT nMessage; // 윈도우즈 메세지
        UINT nCode; // 코맨드 메세지 값
        UINT nID; // 컨트롤 아이디
        UINT nLastID; // range 매크로를 사용할 경우 들어갈 컨트롤 아이디
        UINT nSig; // 메세지 핸들러 함수 프로토타입을 구분하는 식별값
        AFX_PMSG pfn; // 메세지를 처리할 함수 포인터
};

struct AFX_MSGMAP {
        const AFX_MSGMAP* pBaseMap;
        const AFX_MSGMAP_ENTRY* lpEntries;
};
AFX_MSGMAP_ENTRY는 AFX_MSGMAP에 들어갈 항목이며 MFC에서는 ON_COMMAND, ON_WM_LBUTTONDOWN등의 매크로로 해당 멤버의 값을 적절히 넣어주고 있다.각 항목은 AFX_MSGMAP의 lpEntries에 리스트로 관리하여 해당 핸들러가 Map에 등록되어 있는 경우 nSig 타입에 맞는 프로토타입으로 pfn에 해당하는 함수를 호출해주는 구조이다. pBaseMap?은 상속관계에 있는 윈도우 객체에서 derived class에서 핸들러가 없는경우 base class의 Message Map을 가르키는 포인터를 둠으로서 base class쪽에서 핸들링을 할 것인지 찾는 선형구조이다. MFC에서는 이런 처리를 pre-compile단계에서 매크로로 처리하여 핸들링 함수를 동록하는 형태로 만들어서 오버헤드를 줄이도록 설계 되었다. 이 구조체를 이용하는 매크로를 MFC는 다음과 같이 제공한다.
#define DECLARE_MESSAGE_MAP() \\
private: \\
        static const AFX_MSGMAP_ENTRY _messageEntries[]; \\
protected: \\
        static const AFX_MSGMAP messageMap; \\
        virtual const AFX_MSGMAP* GetMessageMap() const;

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \\
        const AFX_MSGMAP* theClass::GetMessagMap() const \\
        {return &theClass::messageMap;} \\
        AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \\
        {&baseClass::messageMap, &theClass::_messageEntries[0]}; \\
        const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \\
        {

#define END_MESSAGE_MAP() \\
        {0, 0, 0, 0, AfxSig_end, (AFX_PMSG) 0} \\
        };
다음 CView를 상속받은 class의 선언을 매크로로 된 상태와 풀어본 상태를 비교해보자. 먼저 해당 class에 Message Map에 관련된 데이터 멤버와 함수를 추가하는 부분
// declare, simplified 
class CTestView : public CView
{
        ...
protected:
        //{{AFX_MSG(CTestView)
        afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
};

// full
class CTestView : public CView
{
        ...
protected:
        //{{AFX_MSG(CTestView)
        afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
        //}}AFX_MSG
private: 
        static const AFX_MSGMAP_ENTRY _messageEntries[];
protected:
        static const AFX_MSGMAP messageMap;
        virtual const AFX_MSGMAP* GetMessageMap() const;
};
다음은 구현부 부분이다.
// declare
BEGIN_MESSAGE_MAP(CTestView, CView)
        //{{AFX_MSG_MAP(CTestView)
        ON_WM_LBUTTONDOWN()
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()

// full
const AFX_MSGMAP* CTestView::GetMessagMap() const {
        return &CTestView::messageMap;
}
AFX_DATADEF const AFX_MSGMAP CTestView::messageMap = {&CView::messageMap, &CTestView::_messageEntries[0]};
const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = {
        {WM_LBUTTONDOWN, 0, 0, 0, AfxSig_vwp, (AFX_PMSG) (AFX_PMSGW) (void (AFX_MSG_CALL CWnd::*)(UINT, CPoint))OnLButtonDown},
        {0, 0, 0, 0, AfxSig_end, (AFX_PMSG) 0}
};
구현 된 것을 보면 위에서 설명한 것과 같이 AFX_MSGMAP구조체인 messageMap의 첫번째 는 CTextView?의 base class의 messageMap을 가르키고 두번째 멤버에는 자신의 메세지맵 리스트의 첫번째 엔트리의 주소를 가지고 있다. message map 리스트에는 ON_WM_LBUTTONDOWN 매크로로 부터 리스트에 항목이 추가되어 있는 것을 볼 수 있다.

MFC Handling Window Messages

윈도우는 메세지를 처리하기 위해서 각각 WNDCLASS에 메세지 처리 프로시저를 등록하여 해당 윈도우의 메세지를 처리하는데 MFC에서는 조금 다르게 운영을 한다. 다음 순서와 그림을 참고하자. mfc_window_message_handling.jpg 1) MFC는 CWinThread?::Run()에서 호출한 CWinThread?::PumpMessage?()에 메세지 루프가 있다. 어떤 이벤트에 대한 메세지가 윈도우를 가진 쓰레드의 메세지 큐에 도착하면 2) 기존의 메세지 루프와 같은 CWinThread?::PumpMessage?()에서 먼저 PreTranslateMessage?()를 호출하며 다음에 TranslateMessage?()와 DispatchMessage?()를 호출한다.
BOOL CWinThread::PumpMessage()
{
        if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
        {
                return FALSE;
        }

        if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
        {
                ::TranslateMessage(&m_msgCur);
                ::DispatchMessage(&m_msgCur);
        }
        return TRUE;
}
3) MFC에서는 해당 쓰레드의 모든 윈도우의 메세지 처리 프로시저는 AfxWndProc?()을 호출한다. 따라서 AfxWndProc?()이 호출이 되며 (자세한것은 MFC InitializingApplication을 참고) 4) MFC는 _AfxCBTFilterHook?()을 이용하여 CWnd가 Create될때 마다 AFX_MODULE_THREAD_STATE의 CHandleMap?를 사용한 m_pmapHWND에 리스트를 등록하고 AfxWndProc?()에서는 CWnd::FromHandlePermanent?()와 HWND를 이용해서 HWND에 맞는 CWnd의 인스턴스를 찾는다. 5) CWnd*를 인자로 받는 AfxCallWndProc?()가 호출되며 안에서 CWnd::WindowProc?()를 호출한다.(가상 함수이긴 하지만 보통 CWnd의 함수를 사용한다. 오버라이딩하는경우는 MFC의 WndProc?방법을 사용하지 않을 경우에 사용한다.) 이전에 만약 다이얼로그의 경우 WM_INITDIALOG를 위해서 다음과 같은 코드로 호출한다.
if (nMsg == WM_INITDIALOG)
        _AfxPreInitDialog(pWnd, &rectOld, &dwStyle);

// delegate to object's WindowProc
lResult = pWnd->WindowProc(nMsg, wParam, lParam);

// more special case for WM_INITDIALOG
if (nMsg == WM_INITDIALOG)
        _AfxPostInitDialog(pWnd, rectOld, dwStyle);
6) CWnd::OnWndMsg?()가 호출되며. WM_COMMAND나 WM_NOTIFY계열경우 특별히 OnCommand?, OnNotify?등의 함수를 호출하며 GetMessageMap?()을 사용하여 Message map을 검색하여 해당 메세지에 대한 핸들러가 cache되었는지 검색하며 정해진 메세지의 핸들러인지 사용자 정의 메세지인지 검색후에 해당 핸들러를 호출한다. 7) GetMessageMap?()은 가상함수로 가장 마지막의 derived class의 Message map부터 검색하며 핸들러가 없다면 Message map리스트의 첫번째 엔트리인 base class의 Message map으로 이동하여 다시 검색한다. 8) 핸들러가 없다면 CWnd::OnWndMsg?()는 FALSE를 리턴하며 CWnd::DefWindowProc?()으로 메세지 처리를 넘긴다. 다음은 6) ~ 8)에 대한 코드이다.
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
        LRESULT lResult = 0;

        // WM_COMMAND계열의 처리
        if (message == WM_COMMAND) 
        {
                if (OnCommand(wParam, lParam))
                {
                        lResult = 1;
                        goto LReturnTrue;
                }
                return FALSE;
        }

        // WM_NOTIFY계열의 호출
        if (message == WM_NOTIFY)
        {
                NMHDR* pNMHDR = (NMHDR*)lParam;
                if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
                        goto LReturnTrue;
                return FALSE;
        }

        // WM_ACTIVATE처리 
        if (message == WM_ACTIVATE)
                _AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));

        // WM_SETCURSOR처리
        if (message == WM_SETCURSOR &&
                _AfxHandleSetCursor(this, (short)LOWORD(lParam), HIWORD(lParam)))
        {
                lResult = 1;
                goto LReturnTrue;
        }

        // 메세지맵을 검색한다.
        const AFX_MSGMAP* pMessageMap; 
        pMessageMap = GetMessageMap(); // 가장 하위 레벨의 클래스의 메세지 맵부터 검색한다.
        UINT iHash; 
        iHash = (LOWORD((DWORD)pMessageMap) ^ message) & (iHashMax-1); // HASH를 이용하여 캐쉬해놓는다.
        AfxLockGlobals(CRIT_WINMSGCACHE);
        AFX_MSG_CACHE* pMsgCache; 
        pMsgCache = &_afxMsgCache[iHash];
        const AFX_MSGMAP_ENTRY* lpEntry;
        // 메세지 처리를 캐쉬를 이용해서 찾는다 .
        if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap) // 캐쉬내에서 찾았다면
        {
                // cache hit
                lpEntry = pMsgCache->lpEntry;
                AfxUnlockGlobals(CRIT_WINMSGCACHE);
                if (lpEntry == NULL)
                        return FALSE;

                // cache hit, and it needs to be handled
                if (message < 0xC000) // 정해진 메세지인지 사용자 정의 메세지 핸들러인지 판단하여 분기한다.
                        goto LDispatch;
                else
                        goto LDispatchRegistered;
        }
        else
        {
                // 캐쉬가 되어 있지 않다면 선형검색을 한다.
                pMsgCache->nMsg = message;
                pMsgCache->pMessageMap = pMessageMap;

                for (; pMessageMap != NULL;        pMessageMap = pMessageMap->pBaseMap) {
                        if (message < 0xC000)
                        {
                                // constant window message
                                if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
                                        message, 0, 0)) != NULL)
                                {
                                        pMsgCache->lpEntry = lpEntry;
                                        AfxUnlockGlobals(CRIT_WINMSGCACHE);
                                        goto LDispatch;
                                }
                        }
                        else
                        {
                                // registered windows message
                                lpEntry = pMessageMap->lpEntries;
                                while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
                                {
                                        UINT* pnID = (UINT*)(lpEntry->nSig);
                                        ASSERT(*pnID >= 0xC000 || *pnID == 0);
                                                // must be successfully registered
                                        if (*pnID == message)
                                        {
                                                pMsgCache->lpEntry = lpEntry;
                                                AfxUnlockGlobals(CRIT_WINMSGCACHE);
                                                goto LDispatchRegistered;
                                        }
                                        lpEntry++;      // keep looking past this one
                                }
                        }
                }

                pMsgCache->lpEntry = NULL;
                AfxUnlockGlobals(CRIT_WINMSGCACHE);
                return FALSE;
        }
        ASSERT(FALSE);      // not reached

LDispatch:
        ASSERT(message < 0xC000);
        union MessageMapFunctions mmf;
        mmf.pfn = lpEntry->pfn;

        int nSig;
        nSig = lpEntry->nSig;
        if (lpEntry->nID == WM_SETTINGCHANGE)
        {
                DWORD dwVersion = GetVersion();
                if (LOBYTE(LOWORD(dwVersion)) >= 4)
                        nSig = AfxSig_vws;
                else
                        nSig = AfxSig_vs;
        }

        // 각각의 핸들러의 시그니처에 맞는 함수의 형태를 호출한다.
        switch (nSig)
        {
        // ... 많이 있다.
        case AfxSig_wp:
                {
                        CPoint point((DWORD)lParam);
                        lResult = (this->*mmf.pfn_wp)(point);
                }
                break;

        }
        goto LReturnTrue;

LDispatchRegistered:    // for registered windows messages
        ASSERT(message >= 0xC000);
        mmf.pfn = lpEntry->pfn;
        lResult = (this->*mmf.pfn_lwl)(wParam, lParam);

LReturnTrue:
        if (pResult != NULL)
                *pResult = lResult;
        return TRUE;
}

Reference

Documents

Website

Books

'MFC' 카테고리의 다른 글

MFC 팁들  (0) 2008.09.01
Creating New Documents, Windows, and Views  (0) 2008.09.01
CWnd::Create 과정....  (1) 2008.09.01
MFC Module State Implementation  (0) 2008.09.01
MFC - DLL에대한 정리  (0) 2008.09.01
CWnd Class  (2) 2008.09.01
MFC_Internels - 비트 교육자료.ppt 상당히 좋음  (1) 2008.08.31