고급 MFC 권영근 airen@soar.snu.ac.kr Introduction 교재 “MFC Internals”, George Shepherd, Addison-Wesley, 1996 MSDN Library, http://msdn.microsoft.com/library/ 선수 강좌 C++ Win32 API Why MFC Internals? 프로그램 debugging 시 종종 MFC 내에서 오류 발생. MFC class 구조에 친숙해져야 함. Class Derivation 많은 class가 derivation 목적으로 정의됨. VC++에서 자동 코드 삽입 => 안전한가? 강의 계획 1. A Conceptual Overview of MFC 2. Basic Windows Support 3. Message Handling in MFC 4. The MFC Utility Classes 5. All Roads Lead to CObject 6. MFC Dialog and Control Classes 7. MFC’s Document/View Architecture 8. Advanced Document/View Internals 9. MFC’s Enhanced User-Interface Classes 10. MFC Dlls and Threads Chap 1. A Conceptual Overview of MFC Contents Application Frameworks 정의 MFC의 역사 MFC의 설계 목적 MFC의 구성 MFC code basics Application Frameworks Framework Application Framework 특정 영역의 작업을 도와 주는 class 집합 특정 OS의 application 개발 작업을 도와 주는 class 집합 MFC (Microsoft Foundation Classes) Windows용 application 개발 작업을 도와 주는 framework MFC의 역사 1989년 AFX group 탄생 목적 Application framework technology development group의 약자 Windows application개발자를 위한 C++과 object-oriented 개념을 적용시킨 tool의 개발 First prototype 실패 Too complex and too different from Windows itself MFC의 역사 (cont’d) 1992년 MFC version 1.0 C/C++ version 7.0과 함께 나옴 60개가 넘는 Windows application 개발을 위한class포함 일반 목적의 여러 class포함 완벽하지 못했음 개발자로부터 많은 불평을 받음 MFC의 역사 (cont’d) 1993년 MFC version 2.0 Visual C++ version 1.0과 Windows NT와 함께 나옴 100개가 넘는 class포함 새로 포함된 내용 새로운 application architecture 새로운 high-level abstractions MFC의 역사 (cont’d) 1993년 MFC version 2.5 Visual C++ version 1.5와 함께 나옴 OLE2.0과 ODBC 지원이 추가됨 1994년 MFC version 3.0 Thread에 대한 기능이 보강됨 MFC의 역사 (cont’d) 1995년 MFC version 3.1 추가된 기능 Simple Messaging Application Programming Interface(MAPI) WinSock MFC version 4.0 Visual C++ version 4.0과 함께 나옴 개발환경의 발전, 재사용성의 향상 MFC의 역사 (cont’d) 1997년 MFC version 4.2 1998년 MFC version 6.0 Visual C++ version 5.0과 함께 나옴 Internet 등을 위한 기능 추가 Visual C++ version 6.0과 함께 나옴 User interface향상 등의 기능이 포함 2002년 MFC version 7.0 Visual C++ .net(7.0) 과 함께 나옴 기존의 각 요소별 추가 및 확장 DHTML(editing, dialog box, …), Ole control, … Static casting and MFC message maps MFC의 역사(cont’d) MFC version과 Visual C++ version MFC version Visual C++ version 6.0 Microsoft Visual C++ version 6.0 4.2 Microsoft Visual C++ versions 4.2 and 5.0 4.0 Microsoft Visual C++ versions 4.0 and 4.1 3.2 Microsoft Visual C++ version 2.2 3.1 Microsoft Visual C++ version 2.1 3.0 Microsoft Visual C++ version 2.0 2.5 Microsoft Visual C++ version 1.5 2.1 Microsoft Visual C++ version 1.0 2.0 Microsoft Visual C++ version 1.0 1.0 Microsoft C/C++ version 7.0 MFC 설계 목적 AFX 그룹의 설계 목적 Real-World Application Simplifying the Windows API Using the existing knowledge of Windows Foundation for large-scale applications Small and fast framework MFC의 구성 MFC class 의 분류 General-purpose classes Windows API classes Application framework classes High-level abstractions MFC의 구성(cont’d) General-purpose Class 프로그램의 일반적 용도를 위한 class CObject class MFC의 최상위 class run-time type information, serialization, diagnostic function, support for dynamic creation 기능 제공 CArchive, CDumpContext, CRuntimeClass 등과 연계되어 작용 MFC의 구성(cont’d) Exception-handling class memory, I/O error 발생 시 처리 CException : base class CArchiveException, CFileException CMemoryException, CResourceException CNotSupportedException, CUserException COleException, CDBException MFC의 구성(cont’d) Collection class Array : CByteArray, CWordArray, CDWordArray, CPtrArray, CObArray, CStringArray, CUnitArray Linked list : CObList, CPtrList, CStringList Map : CMapPtrToWord, CMapPtrToPtr, CMapStringToOb, CMapStringToPtr, CMapStringToString, CMapWordToOb, CMapWordToPtr MFC의 구성(cont’d) Dynamic string class CString concatenation, comparison, assignment 등의 기본 연산 제공 File class CFile, CStdioFile, CMemFile 추상적으로 disk상의 파일 제어, 실제로는 memory상의 파일 제어 MFC의 구성(cont’d) Time class CTime, CTimeSan 기타 CPoint, CSize, CRect : Windows structure MFC의 구성(cont’d) Windows API class Application 관련 class CCmdTarget : message 처리 CCmdUI : user interface의 update CWinThread : MFC program의 실행 thread를 의미, 즉 program의 main을 포함 CWinApp : CWinThread의 파생 class으로서 standard windows application을 표현 MFC의 구성(cont’d) Window 관련 class CWnd : CCmdTarget의 파생 class이므로 message를 handle. 윈도우를 다루는 API 포함. CFramWnd, CMDIFrameWnd : main frame window로서 message를 받는 첫 윈도우 CDialog, 공통다이어로그박스(CFileDialog, CColorDialog, CFontDialog, CPrintDialog, CFindReplaceDialog) CDataExchange : DDX/DDV MFC의 구성(cont’d) CPropertySheet, CPropertyPage Controls : CButton, CEdit, … CMenu GDI 관련 class CDC, CPaintDC, CWindowDC, CClientDC, CMetaFileDC CPen, CBrush, CFont, ... MFC의 구성(cont’d) Application framework class Document/View Architecture CDocTemplate, CSingleDocTemplate, CMultiDocTemplate : document와 view를 연결 CDocument : data를 관리 CView : data를 redering하여 보여 줌 Context-Sensitive Help MFC의 구성(cont’d) High-level abstraction Enhanced Views Splitter Window CScrollView, CFormView CEditView, CListView, CRichEditView, CTreeView CSplitterWnd : dynamic, static Control Views CToolBar, CStatusBar, CDialogBar MFC Code Basics Class Declaration Subsections MFC library 개발 팀의 코딩 규칙 Not based public/protected/private Private 변수는 거의 사용하지 않는다. Header / source file // // // // // Constructors Attributes Operations Overridables Implementation Example of Comments class CStdioFile : public CFile { DECLARE_DYNAMIC(CStdioFile) public: // Constructors CStdioFile(); ... // Attributes FILE* m_pStream; // stdio FILE ... // Operations virtual void WriteString(LPCTSTR lpsz); ... virtual LPTSTR ReadString(LPTSTR lpsz, UINT nMax); ... // Implementation public: ... }; MFC Comments Class Declaration Subsections MFC library 개발 팀의 코딩 규칙 Class header file의 각 항목의 의미 // Constructors C++ constructors, any other initialization 예) CWnd::Create 대개는 public // Attributes 대개는 documented public data members Member functions(위의 data를 조작하는) : Get / Set 함수들 MFC Code Basics (cont’d) // Operations // Overridables Documented member functions 대개는 public, non-const : side effects 상속 받은 class가 override한 functions Pure virtual functions // Implementation Implementation detail Undocumented 대개는 protected 주의 : may change in future versions of MFC MFC Code Basics (cont’d) Variable Naming (common) Type Prefix Example Comment char c cDirSeparator BOOL b blsSending int n nVariableCnt UINT n nMyUnsigned WORD w wListID LONG l lAxisRatio DWORD dw dwPackedmessage * (pointer) p pWnd FAR * lp lpWnd LPSTR handle lpsz h lpszFileName hWnd Z indicates NULL terminated. callback lpfn lpfnHookProc Pointer to a function MFC Code Basics (cont’d) Variable Naming (MFC extensions) Class Prefix Example CRect rect rectScroll CPoint pt ptMouseClick CSize sz szRectangle CString str strFind CWnd Wnd WndControl CWnd* pWnd pWndDialog MFC Code Basics (cont’d) Symbol Naming Type Prefix Example Range Shared by multiple resources IDR_ IDR_MAINFRAME 1-0x6FFF Dialog resource IDD_ IDD_ABOUT 1-0x6FFF Dialog resource help context ID (for context-sensitive help) HIDD_ HIDD_HELP_ABOU T 0x2001-0x26FF Bitmap resource IDB_ IDB_SMILEY 1-0x6FFF Cursor resource Icon resource IDC_ IDC_HAND 1-0x6FFF MFC Code Basics (cont’d) Symbol Naming Type Prefix Example Range Menu or toolbar command ID_ ID_CIRCLE_TOOL 0x8000-0xDFFF Command help context HID_ HID_CIRCLE_TOOL 0x1800-0x1DFF Message box prompt IDP_ IDP_FATALERROR 8-0xDFFF Message box help context HIDP_ HIDP_FATALERROR 0x3008-0x3DFF Control in dialog template IDC_ IDC_COMBO1 8-0xDFFF String resource IDS_ IDS_ERROR12 1-0x7FFF Chap. 2 Basic Windows Support Contents Introduction MFC versus C/SDK Basic Application Components Find WinMain() Hidden Cool Stuff CWinApp CWnd Window handles & Window objects Registering Window Classes MFC’s Windows Hooks MFC’s Message Pump MFC’s GDI Support Introduction MFC 200개 이상의 클래스들의 거대한 집합 But, MFC has also “Basic Windows Support” A Windows program is still a Windows program 어떤 언어(C, C++, Delphi, …)나 framework(MFC, OWL, …) 를 이용하든지 기본적인 요소들이 구현된다. Basic windows application support WinMain, window class 등록, 메시지 루프, … Issue MFC가 어떻게 Windows application을 만드는가 The application itself Windows Message handling The Graphics Device Interface (GDI) MFC vs. C/SDK Motivation 모든 Windows application은 다음 2개의 component를 포함한다. C/SDK 개발 환경 main application itself message를 핸들하는 하나 이상의 window copy & paste Time-consuming & inefficient C++/MFC 개발 환경 OOP 활용 : inheritance & encaptulation 필요한 부분만 변경 Boilerplate Code 왜 필요한가? Windows is event-driven OS Imposes a grate deal of overhead Windows OS H/W 와 응용 프로그램을 연결하는 위치 Application에게 발생하는 이벤트를 알림 이벤트를 다루기 위해 상당한 양의 코드가 항상 필요 메시지 처리를 위한 작업들 Set up a message handler and register it RegiserClass() Windows가 application의 instance들을 추적 application은 Windows에게 메시지를 요청(ask)하고, 처리(dispatch)한다. application이 종료될 때까지 위 작업을 반복한다. Application의 준비 1. WinMain() 함수 프로그램의 시작점 Windows 로부터 프로그램을 실행하는데 필요한 정보를 얻어 오는 통로 2. 적어도 하나의 main window class를 등록 3. 4. User interface를 제공 Message loop를 설정 Some initialization and setup 5. 현재 instance의 handle, 직전에 실행된 instance의 handle, command line argument, window의 모습(최대화, 최소화, …) Application specific, instance specific Message handler를 제공 최소한 WM_DESTROY 처리 -> WM_QUIT 생성 Application의 기본 요소 Message Handler Main 함수 Application specific Initialization (Window class 등록) Instance specific Initialization (Main window(UI)생성, 보여줌) Message loop Source 1 : C/SDK #include <windows.h> HANDLE hInst; /* current instance */ LRESULT CALLBACK MainWndProc(HANDLE hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_LBUTTONDOWN: MessageBox(hWnd,“Left mouse button clicked”, NULL, MB_OK); break; case WM_DESTROY: PostQuitMessage(0); break; default: /* Passes it on if unprocessed */ return (DefWindowProc(hWnd, message, wParam, lParam)); } return 0; } BOOL InitApplication(HANDLE hInstance){ WNDCLASS wc; wc.style = 0; /* Class style(s) */ wc.lpfnWndProc = MainWndProc; /* Message handler */ wc.cbClsExtra = 0; /* No per-class extra data */ wc.cbWndExtra = 0; /* No per-window extra data */ wc.hInstance = hInstance; /* Application that owns the class*/ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; /* Name of menu */ wc.lpszClassName = “MinimalWClass”; /* Name of window class */ return (RegisterClass(&wc)); } BOOL InitInstance(HANDLE hInstance, int nCmdShow) { HWND hWnd; /* Main window handle */ hInst = hInstance; // needed for loading resources // hWnd = CreateWindow( “MinimalWClass”, “Minimal”, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); /* /* /* /* /* /* /* /* /* /* Window class */ Caption */ Window style */ Default horizontal pos. */ Default vertical pos. */ Default width */ Default height. */ No parent */ Use the window class menu */ This instance owns the window. */ if (!hWnd) return (FALSE); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return (TRUE); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; /* message */ if (!hPrevInstance) { /* First instance? */ if (!InitApplication(hInstance)) /* Shared stuff */ return (FALSE); /* cannot initialize */ } if (!InitInstance(hInstance, nCmdShow)) return (FALSE); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return(msg.wParam);/*Returns the value from PostQuitMessage*/ } Source 2 : MFC #include <afxwin.h> // MFC 주요 클래스 선언 class CGenericApp : public CWinApp { public: virtual BOOL InitInstance(); }; class CGenericWindow : public CFrameWnd { public: CGenericWindow() { Create(NULL, “Generic”); } afx_msg void OnLButtonDown(UINT nFlags, CPoint point); DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(CGenericWindow, CFrameWnd) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() void CGenericWindow::OnLButtonDown(UINT nFlags, CPoint point) { MessageBox(“Left mouse button pressed…”, NULL, MB_OK); } BOOL CGenericApp::InitInstance() { m_pMainWnd = new CGenericWindow(); m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CGenericApp GenericApp; C/SDK와 MFC의 비교 WinMain() Function Window class 등록 C/SDK : 존재 MFC : where? ( part of application framework ) C/SDK : 존재 MFC : where? Message loop C/SDK : 존재 MFC : where? Additional functions : idle processing, PreTranslateMessage() C/SDK와 MFC의 비교 (cont’d) Instance initialization C/SDK : 존재 MFC : 존재, 그러나 언제 실행? Message handling C/SDK : 존재 MFC : 존재, 그러나 어떻게 command-routing, message-dispatch? Member functions + Message map AfxWndProc() : 공통 window procedure Basic MFC Application Components Windows application의 두 가지 components Message pump Window procedure MFC에서의 구현 방법 CWinApp Application을 나타내는 class application-specific : 초기화, window 생성, 메시지 루프 CWnd Window를 나타내는 class window-specific : 메시지 핸들링 CWinApp (“AfxWin.h”) CWinApp : public CWinThread { public: // Constructor CWinApp(LPCTSTR lpszAppName = NULL); to EXE name // app name defaults // Attributes // Startup args (do not change) HINSTANCE m_hInstance; HINSTANCE m_hPrevInstance; // not used LPTSTR m_lpCmdLine; int m_nCmdShow; // Running args (can be changed in InitInstance) LPCTSTR m_pszAppName; // human readable name // (from constructor or AFX_IDS_APP_TITLE) public: // set in constructor to override default LPCTSTR m_pszExeName; // executable name (no spaces) LPCTSTR m_pszHelpFilePath; // default based on module path LPCTSTR m_pszProfileName; // default based on app name // Overridables // hooks for your initialization code virtual BOOL InitApplication(); void SetCurrentHandles(); // overrides for implementation virtual BOOL InitInstance(); virtual int ExitInstance(); // return app exit code virtual int Run(); virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing virtual LRESULT ProcessWndProcException (CException* e, const MSG* pMsg); public: virtual ~CWinApp(); protected: //{{AFX_MSG(CWinApp) afx_msg void OnAppExit(); afx_msg void OnUpdateRecentFileMenu(CCmdUI* pCmdUI); afx_msg BOOL OnOpenRecentFile(UINT nID); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; CWinApp AFXWIN.h 멤버 변수와 멤버 함수 WinMain()에 전달된 command line parameter 를 관리할 변수 m_hInstance : the current instance handle m_hPrevInstance : the previous instance handle m_lpCmdLine : the command line parameters m_nCmdShow : the show window flag CWinApp (cont’d) m_pszAppName : application name 관련 pointer m_pszExeName : executable file name m_pszHelpFilePath : the path to help file m_pszProfileName : application profiles name 프로그램 실행 시 주어진 command line parameter 유지 CCommandLineInfo class 이용 (AFXWIN.H) CWinApp (cont’d) InitInstance() : instance-specific 초기화 ExitInstance() : instance 종료 시 처리 Run() : message pump 수행 OnIdle() : Message queue가 비었을 때, Run 함수에 의해 호출 CWnd AFXWIN.H 2가지 기능 수행 Wrapping the regular Windows API 예: Create(), ShowWindow(), … Higher-level MFC-related functionality 예: default message handling CWnd ( 기능 1 ) Wrapping the Windows API m_hWnd : regular API-level window handle 을 나타내기 위한 멤버변수 window handle을 인자로 갖는 거의 모든 API를 멤버 함수로 가진다. 예) API HWND hWnd; ShowWindow(hWnd,SW_SHOWNORMAL); MFC CWnd * pWnd; pWnd->ShowWindow(SW_SHOWNORMAL); AFXWIN2.INL : API 의 호출 _AFXWIN_INLINE BOOL CWnd::ShowWindow(int nCmdShow) { ASSERT(::IsWindow(m_hWnd)); return ::ShowWindow(m_hWnd, nCmdShow); } CWnd ( 기능 2 ) Higher-level MFC-related functionality CObject -> CCmdTarget -> CWnd CObject derivation CCmdTarget derivation Dynamic run-time information Serialization MFC’s message-routing scheme Default message handling 제공 예 : default message handling _AFXWIN_INLINE void CWnd::OnActivate(UINT, CWnd*, BOOL) { Default(); } LRESULT CWnd::Default() { // call DefWindowProc with the last message _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); return DefWindowProc(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam); } Turning Window Handles into Window Objects High Level Application MFC object CHandleMap class Low Level Windows OS Window handle 계층적 관계 High level : MFC object Low level : Window handle application 입장에서는 MFC object를 이용 Windows OS는 handle을 이용 MFC는 어떤 handle에 어떤 object가 연결되어 있는지를 알아야 함 CHandleMap class를 이용하여 구현 CHandleMap AFXSTAT_.H Mapping window handle to MFC object Handle이 주어지면 해당 object를 찾음 Windows OS가 callback 함수를 호출할 때, window handle을 parameter로 호출 그러나, MFC는 해당 CWnd- 파생 클래스 객체를 가지고 작업 CHandleMap (cont’d) 멤버변수 CMapPtrToPtr m_permanantMap Permanent map 명시적으로 객체가 생성될 때 정보가 추가됨(CWnd’s::Create()) 객체가 종료될 때 정보가 제거됨 (CWnd’s::OnNcDestroy()) cf) WM_NCCREAT -> WM_CREAT -> … -> WM_DESTROY -> WM_NCDESTROY CMapPtrToPtr m_temporaryMap Temporary map 임시로 생성해야 할때(ex: CWnd::GetActiveWindow()) 실제로는 CWnd::HandleMap 에서 필요할 때 생성 OnIdle()에서 삭제 => DeleteTempMap() 호출 CHandleMap (cont’d) 멤버함수 CWnd::FromHandle(HWND hWnd) 주어진 window handle에 mapping되는 object의 pointer를 얻을 때 사용 hWnd를 wrap하고 있는 CWnd pointer return 만약 없으면 hWnd를 wrap하는 temporary CWnd return 획득하면 역으로 object를 통해 hWnd의 접근이 용이 ( CWnd::m_hWnd 멤버 변수 ) Temporary map 예 “AFXWIN2.INL” _AFXWIN_INLINE CWnd* PASCAL CWnd::GetActiveWindow() { return CWnd::FromHandle(::GetActiveWindow()); } “Wincore.cpp” CWnd* PASCAL CWnd::FromHandle(HWND hWnd) { CHandleMap* pMap = afxMapHWND(TRUE); CWnd* pWnd = (CWnd*)pMap->FromHandle(hWnd); return pWnd; } Temporary map 예 (cont’d) “Winhand.cpp” CObject* CHandleMap::FromHandle(HANDLE h) { if (h == NULL) return NULL; CObject* pObject = LookupPermanent(h); if (pObject != NULL) return pObject; // return permanent one else if ((pObject = LookupTemporary(h)) != NULL) { HANDLE* ph = (HANDLE*)((BYTE*)pObject + m_nOffset); ph[0] = h; if (m_nHandles == 2) { ph[1] = h; } return pObject; // return current temporary one } Temporary map 예 (cont’d) CObject* pTemp = NULL; TRY { pTemp = m_pClass->CreateObject(); m_temporaryMap.SetAt((LPVOID)h, pTemp); } CATCH_ALL(e) { AfxSetNewHandler(pnhOldHandler); AfxEnableMemoryTracking(bEnable); THROW_LAST(); } END_CATCH_ALL AfxSetNewHandler(pnhOldHandler); AfxEnableMemoryTracking(bEnable); HANDLE* ph = (HANDLE*)((BYTE*)pTemp + m_nOffset); ph[0] = h; if (m_nHandles == 2) ph[1] = h; return pTemp; } Other Mapping AFX_MODULE_THREAD_STATE m_pmapHWND m_pmapHMENU device context handles CDC objects m_pmapHGDIOBJ menu handles CMenu objects m_pmapHDC window handles CWnd objects GDI object handles CGDIObjects m_pmapHIMAGELIST image list handles CImageList objects Attaching & Detaching Window handles과 CWnd-derived objects를 연관시켜주는 함수 CWnd::Attach() 주어진 window handle을 CWnd::m_hWnd에 대입 MFC’s permanent map에 정보 추가 CWnd::Detach() CWnd::m_hWnd를 NULL로 만듬 MFC’s permanent map에서 정보 제거 CWnd::Attach BOOL CWnd::Attach(HWND hWndNew) { ASSERT(m_hWnd == NULL); // only attach once, detach on destroy ASSERT(FromHandlePermanent(hWndNew) == NULL); // must not already be in permanent map if (hWndNew == NULL) return FALSE; CHandleMap* pMap = afxMapHWND(TRUE); ASSERT(pMap != NULL); pMap->SetPermanent(m_hWnd = hWndNew, this); AttachControlSite(pMap); return TRUE; } CWnd::Detach HWND CWnd::Detach() { HWND hWnd = m_hWnd; if (hWnd != NULL) { CHandleMap* pMap = afxMapHWND(); // don't create if not exist if (pMap != NULL) pMap->RemoveHandle(m_hWnd); m_hWnd = NULL; } m_pCtrlSite = NULL; return hWnd; } 주의 Multiple threads Permanent , temporary map 모두 thread 단위로 저장 전달할 경우 object 대신 HANDLE 을 보내는 것이 바람직 Thread 1 Thread 2 Window create Window create 접근 불가 Find WinMain() APPCORE.CPP CWinApp 생성자 호출 APPMODULE.CPP _tWinMain() extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // call shared/exported WinMain return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); } Find WinMain() (cont’d) WINMAIN.CPP AfxWinMain() int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { int nReturnCode = -1; CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp(); // AFX internal initialization if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)) goto InitFailure; // App global initializations (rare) if (pApp != NULL && !pApp->InitApplication()) goto InitFailure; Find WinMain() (cont’d) // Perform specific initializations if (!pThread->InitInstance()) { if (pThread->m_pMainWnd != NULL) { pThread->m_pMainWnd->DestroyWindow(); } nReturnCode = pThread->ExitInstance(); goto InitFailure; } nReturnCode = pThread->Run(); AfxWinTerm(); return nReturnCode; } Find WinMain() (cont’d) APPINIT.CPP AfxWinInit() BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // handle critical errors and avoid Windows message boxes SetErrorMode(SetErrorMode(0) | SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX); // set resource handles AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); pModuleState->m_hCurrentInstanceHandle = hInstance; pModuleState->m_hCurrentResourceHandle = hInstance; Find WinMain() (cont’d) // fill in the initial state for the application CWinApp* pApp = AfxGetApp(); if (pApp != NULL) { // Windows specific initialization (not done if no CWinApp) pApp->m_hInstance = hInstance; pApp->m_hPrevInstance = hPrevInstance; pApp->m_lpCmdLine = lpCmdLine; pApp->m_nCmdShow = nCmdShow; pApp->SetCurrentHandles(); } // initialize thread specific data (for main thread) if (!afxContextIsDLL) AfxInitThread(); return TRUE; } Find WinMain() (cont’d) InitApplication() InitInstance() application-specific, but do nothing instance-specific, virtual Run() CWinApp 생성자 CWinApp::CWinApp(LPCTSTR lpszAppName) { if (lpszAppName != NULL) m_pszAppName = _tcsdup(lpszAppName); else m_pszAppName = NULL; // initialize CWinThread state AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE(); AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread; pThreadState->m_pCurrentWinThread = this; m_hThread = ::GetCurrentThread(); m_nThreadID = ::GetCurrentThreadId(); // initialize CWinApp state ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please pModuleState->m_pCurrentWinApp = this; ASSERT(AfxGetApp() == this); CWinApp 생성자 (cont’d) // in non-running state until WinMain m_hInstance = NULL; m_pszHelpFilePath = NULL; m_pszProfileName = NULL; m_pszRegistryKey = NULL; m_pszExeName = NULL; m_pRecentFileList = NULL; m_pDocManager = NULL; m_atomApp = m_atomSystemTopic = NULL; m_lpCmdLine = NULL; m_pCmdInfo = NULL; // other initialization m_bHelpMode = FALSE; m_nSafetyPoolSize = 512; // default size } InitApplication() BOOL CWinApp::InitApplication() { if (CDocManager::pStaticDocManager != NULL) { if (m_pDocManager == NULL) m_pDocManager = CDocManager::pStaticDocManager; CDocManager::pStaticDocManager = NULL; } if (m_pDocManager != NULL) m_pDocManager->AddDocTemplate(NULL); else CDocManager::bStaticInit = FALSE; return TRUE; } InitInstance() BOOL CWinApp::InitInstance() { return TRUE; } Run() int CWinThread::Run() { // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage( &m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } Run (cont’d) // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage (&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable } MFC state information 프로그램 종료까지 유지되는 정보 AFX_MODULE_STATE class AFXSTAT_.H 보유하는 정보 Main window handles Resource, module handles Memory allocation tracking ODBC support, OLE support, exception handling Hidden Cool Stuff 2 issues window class의 등록 Windows hook와 MFC window의 연결 Registering Window Classes Windows application OS 에 적어도 하나 이상의 window class를 등록해야만 한다. window class window의 기본적인 성질 정의 appearance ( via some flag ) behavior ( via a callback function ) Registering Window Classes (cont’d) MFC의 window class 등록 4개의 기본 window class 등록 regular child windows a control bar window an MDI frame window a window for an SDI or MDI child window 기타 common controls Registering Window Classes (cont’d) Style WNDCLASSes and MFC Style of window LpfnWndProc window proc, must be AfxWndProc CbClsExtra not used ( 0 ) CbWndExtra not used ( 0 ) HInstance automatically filled with AfxGetInstanceHandle HIcon icon for frame window HCursor cursor for when mouse is over window HbrBackgroundbackground color LpszMenuName not used ( NULL ) LpszClassName class name Registering Window Classes (cont’d) AfxWnd Used for all child windows (CWnd::Create) Class style : CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW No icon Arrow cursor No background color AfxFrameOrView Used for frame windows and views Class style : CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW Icon AFX_IDI_STD_MDIFRAME Arrow cursor COLOR_WINDOW background color Registering Window Classes (cont’d) AfxMDIFrame Used for MDI frame window (CMDIFrameWnd::Create) Class style : CS_DBLCLKS Icon Icon AFX_IDI_STD_MDIFRAME Arrow cursor No background color AfxControlBar Used for standard control bar implementation Class style : 0 No icon Arrow cursor Gray background color (COLOR_BTNFACE) Registering Window Classes (cont’d) AfxDeferRegisterClass() AFXIMPL.H AfxEndDeferRegisterClass() WINCORE.CPP #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass) BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister); Class 등록 예 “WINCORE.CPP” BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam) { // allow modification of several common create parameters 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; Class 등록 예 (cont’d) if (!PreCreateWindow(cs)) { PostNcDestroy(); return FALSE; } AfxHookWindowCreate(this); HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams); if (hWnd == NULL) GetLastError()); if (!AfxUnhookWindowCreate()) PostNcDestroy(); if (hWnd == NULL) return FALSE; ASSERT(hWnd == m_hWnd); // should have been set in send msg hook return TRUE; } Class 등록 예 (cont’d) BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs) { if (cs.lpszClass == NULL) { // make sure the default window class is registered VERIFY(AfxDeferRegisterClass(AFX_WND_REG)); // no WNDCLASS provided - use child window default ASSERT(cs.style & WS_CHILD); cs.lpszClass = _afxWnd; } return TRUE; } MFC’s Message Pump CWinApp::Run() 이 함수가 실행되면서 실제 메시지 펌프가 동작하게 됨 이는 CWinApp가 CWinThread로 부터 상속받기 때문에 가능한 것임 Thread 종류 Worker thread : 일반적으로 말하는 thread Interface thread : Message pump MFC’s GDI 지원 GDI (Graphical Device Interface) Windows OS상의 풍부한 그리기 작업 장치 독립적 지원 : GDI + Device driver DC (Device Context) GDI의 핵심적인 개념 GDI 함수의 parameter 스크린, 프린터 등을 표시 구조체 : 객체 + 속성 + 모드 Device Contexts CDC CPaintDC CWindowDC 전체 스크린을 표시 (client area+frame) GetWindowDC() ~ ReleaseDC() CClientDC painting 작업이 발생할 때 BeginPaint() ~ EndPaint() Client area를 표시 GetClientDC() ~ ReleaseDC() CMetaFileDC 메타파일에 대한 그리기 작업 생성자 ~ Create() ~ DeleteMetaFile() Graphic Objects CGdiObject : Base class CPen CBrush CFont CBitmap CPalette CRgn Win32 API의 함수들을 wrapping 예제 CMyWnd::OnPaint() { CPaintDC paintDC(this); CPen* pOldPen; CPen bluePen(PS_SOLID, 25, RGB(0, 0, 255)); pOldPen = paintDC.SelectObject(&bluePen); paintDC.MoveTo(1, 1); paintDC.LineTo(100, 100); paintDC.SelectObject(pOldPen); } Chap. 3 Message Handling Contents Introduction Window messages Message mapping MFC가 message map을 이용하는 방법 Message loop의 hooking Introduction Message handling C/SDK Switch/case 문을 통해 구현 MFC Switch/case 문은 없다 Callback function은 어떻게 정의되는가 Basic Components MFC의 message handling 구조 CCmdTarget class Message maps MFC message maps에 대한 의문 어떻게 switch/case문을 대체하는가 어떤 data structure로 정의되는가 어떻게 message map이 작동하는가 어떻게 message가 연결되는가 Window Messages 3개 부분으로 구성됨 Unsigned integer WPARAM 실제 메시지를 나타냄 Word(32bits)크기의 parameter 메시지에 따른 특정 데이타 LPARAM 4byte parameter 메시지에 따른 특정 데이타 Window Messages (cont’d) 핸들링 해야 할 메시지의 분류 Windows message Control notification WM_ 로 시작하는 메시지들 (WM_COMMAND 제외) 일반적으로 window, view 등에서 핸들링 WM_COMMAND 중 notify, WM_NOTIFY Control, 자식 window 가 부모 윈도우에게 전달하는 메시지들 예) EN_CHANGED 를 포함한 WM_COMMAND 메시지 Command message 메뉴, 툴바, 엑셀레이터 등으로 인한 메시지 Window Messages (cont’d) 핸들링 클래스 Windows message, control notification CWnd 로부터 파생된 클래스들 : HWND 를 포함 Command message 다양한 종류 : CCmdTarget 로부터 파생된 클래스들 예) open 명령 핸들링 : application UI Objects and Command IDs Message Handling in C/SDK Windows program의 정수 While (GetMessage(&msg, NULL, NULL, NULL)) { // Translates virtual key codes TranslateMessage(&msg); // Dispatches message to window DispatchMessage(&msg); } // Returns the value from PostQuitMessage Return (msg.wParam); Message Handling in C/SDK (cont’d) WNDCLASS ws; … ws.lpfnWndProc = WndProc; … RegisterClass(ws); LRSULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: … break; case WM_COMMAND: switch (wParam) { case IDM_ABOUT: … } } Message Handling in MFC 방법 1 Polymorphism : 가상 멤버 함수 이용 심각한 메모리 낭비 방법 2 Message maps MFC 의 특징 Message Mapping Internals 두 부분으로 구성 CCmdTarget Window message나 command를 받기 위해 반드시 상속받아야 하는 class Message map Window message와 이를 처리하는 class member function을 연관시켜주는 mechanism CCmdTarget Class 메시지의 처리 CCmdTarget의 파생 클래스 예 CWnd class CDocument CWinApp Message Map Data structures AFX_MSGMAP_ENTRY(AFXWIN.H) Message map table의 한 element(entry) Message에 대한 정보와 message handler에 대한 정보를 저장 struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's UINT nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) }; Message Map(contd.) AFX_MSGMAP(AFXWIN.H) 실제 message map struct AFX_MSGMAP { #ifdef _AFXDLL const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); #else const AFX_MSGMAP* pBaseMap; #endif const AFX_MSGMAP_ENTRY* lpEntries; }; Message Map Macros Macros (AFXWIN.H) DECLARE_MESSAGE_MAP Header file에서 정의 BEGIN_MESSAGE_MAP / END_MESSAGE_MAP Implementation file에서 정의 #define DECLARE_MESSAGE_MAP() \ private: \ static const AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA const AFX_MSGMAP messageMap; \ virtual const AFX_MSGMAP* GetMessageMap() const; \ Message Map Macros (cont’d) #define BEGIN_MESSAGE_MAP(theClass, baseClass) \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &baseClass::messageMap, &theClass::_messageEntries[0] }; \ AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ {\ #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ Massage Map 예 class CTestView : public Cview { protected: //{{AFX_MSG(CTestView) afx_msg void OnLButtonDblClk(UINT nFlags, Cpoint point); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; class CTestView : public Cview { protected: afx_msg void OnLButtonDblClk(UINT nFlags, Cpoint point); private: static const AFX_MSGMAP_ENTRY _messageEntries[]; protected: static const AFX_MSGMAP messageMap; virtual const AFX_MSGMAP* GetMessageMap() const; }; Massage Map 예 (cont’d) BEGIN_MESSAGE_MAP(CTestVIew, Cview) //{{AFX_MSG_MAP(CTestView) ON_COMMAND(ID_STRING_CENTER, OnStringCenter) ON_WM_LBUTTONDBLCLK() //}}AFX_MSG_MAP END_MESSAGE_MAP() const AFX_MSGMAP* CTestView::GetMessageMap() const { return &CTestView::messageMap; } AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CTestView::messageMap = { &CView::messageMap, &CTestView::_messageEntries[0] }; const AFX_MSGMAP_ENTRY CTestVIew::_messageEntries[] = { ON_COMMAND(ID_STRING_CENTER, OnStringCenter) ON_WM_LBUTTONDBLCLK() {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; Message Map Entry Macro AFXMSG_.H #define ON_COMMAND(id, memberFxn) \ { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, \ AfxSig_vv, (AFX_PMSG)&memberFxn }, #define ON_WM_LBUTTONDBLCLK() \ { WM_LBUTTONDBLCLK, 0, 0, 0, AfxSig_vwp, \ (AFX_PMSG)(AFX_PMSGW) \ (void (AFX_MSG_CALL CWnd::*) \ (UINT, CPoint))&OnLButtonDblClk }, Message Map Entry Macro (cont’d) Message Type Macro Form Predefined Windows messages ON_WM_XXXX Commands ON_COMMAND Update commands ON_UPDATE_COMMAND_UI Control notifications ON_XXXX User-defined message ON_MESSAGE Registered Windows message ON_REGISTERED_MESSAGE Range of command IDs ON_COMMAND_RANGE Range of command Ids for updating ON_UPDATE_COMMAND_UI_RANGE Range of control IDs ON_CONTROL_RANGE User-define Message // inside the class declaration afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); For example: #define WM_MYMESSAGE (WM_USER + 100) BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass) ON_MESSAGE(WM_MYMESSAGE, OnMyMessage) END_MESSAGE_MAP() CWnd* pWnd = ...; pWnd->SendMessage(WM_MYMESSAGE); Handlers for Message-Map Ranges 종류 ON_COMMAND_RANGE ON_UPDATE_COMMAND ON_CONTROL_RANGE Message-Map Entry ID 는 연속적이어야 한다. BEGIN_MESSAGE_MAP(CMyApp, CWinApp) ON_COMMAND_RANGE(ID_MYCMD_ONE, ID_MYCMD_TEN, OnDoSomething) ON_CONTROL_RANGE(BN_CLICKED, IDC_BUTTON1, IDC_BUTTON10, OnButtonClicked) END_MESSAGE_MAP( ) Handlers for Message-Map Ranges Handlers ... void CMyDialog::OnButtonClicked( UINT nID ) { int nButton = nID - IDC_BUTTON1; ASSERT( nButton >= 0 && nButton < 10 ); // ... } MFC의 Message Map이용 MFC-based program 두 종류의 메시지를 처리 regular window messages commands WM_MOUSEMOVE, … WM_COMMAND Message-mapping architecture의 이해 두 종류 메시지를 각각 추적 How to be wired 16-bit version AfxWndProc() Message handling procedure로 등록된 함수 32-bit version DefWindowProc() 가 message handler로 등록 Message hook방법을 써서 결국에는 AfxWndProc()가 message를 처리하게 함 Hooking function _AfxCbtFilterHook(), _AfxStandardSubclass() 3D Control을 지원하기 위함 How to be wired (cont’d) “WinCore.cpp” BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister) { // mask off all classes that are already registered AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); fToRegister &= ~pModuleState->m_fRegisteredClasses; if (fToRegister == 0) return TRUE; LONG fRegisteredClasses = 0; // common initialization WNDCLASS wndcls; memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults wndcls.lpfnWndProc = DefWindowProc; wndcls.hInstance = AfxGetInstanceHandle(); wndcls.hCursor = afxData.hcurArrow; How to be wired (cont’d) BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam) { … AfxHookWindowCreate(this); } void AFXAPI AfxHookWindowCreate(CWnd* pWnd) { if (pThreadState->m_hHookOldCbtFilter == NULL) { pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL, ::GetCurrentThreadId()); if (pThreadState->m_hHookOldCbtFilter == NULL) AfxThrowMemoryException(); } pThreadState->m_pWndInit = pWnd; } How to be wired (cont’d) LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) { … // subclass the window if not already wired to AfxWndProc if (!bAfxWndProc) { // subclass the window with standard AfxWndProc oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc); ASSERT(oldWndProc != NULL); *pOldWndProc = oldWndProc; } … } Message Handling AfxWndProc()(WINCORE.CPP) WM_QUERYAFXWNDPROC MFC’s message map을 사용하는 MFC window인지를 확인할 수 있는 message 다른 message에 대해서는 AfxCallWndProc() 함수를 호출 LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) { if (nMsg == WM_QUERYAFXWNDPROC) return 1; CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); } Message Handling (cont’d) AfxCallWndProc()(WINCORE.CPP) Thread state 구조체에 message의 정보를 저장 WM_INITDIALOG처리 Auto-center dialog위한 처리를 수행 Window object의 window procedure호출 CWnd::WindowProc()(WINCORE.CPP) 내부적으로 OnWndMsg()를 호출 OnWndMsg()가 처리하지 못하면 CWnd::DefWindowProc()를 호출 Message Handling (cont’d) LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); MSG oldState = pThreadState->m_lastSentMsg; // save for nesting pThreadState->m_lastSentMsg.hwnd = hWnd; pThreadState->m_lastSentMsg.message = nMsg; pThreadState->m_lastSentMsg.wParam = wParam; pThreadState->m_lastSentMsg.lParam = lParam; CRect rectOld; DWORD dwStyle = 0; 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); pThreadState->m_lastSentMsg = oldState; return lResult; } Message Handling (cont’d) CWnd::WndowProc()(WINCORE.CPP) virtual 이므로 override 가능 성능 향상을 위해 message-mapping system을 거치지 않고 처리해야 하는 메시지에 대한 핸들링 가능 “WinCore.Cpp” LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // OnWndMsg does most of the work, except for DefWindowProc call LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult)) lResult = DefWindowProc(message, wParam, lParam); return lResult; } Message Handling (cont’d) CWnd::OnWndMsg()(WINCORE.CPP) 특별히 처리하는 message WM_COMMAND, WM_NOTIFY WM_ACTIVATE, WM_SETCURSOR 기타 다른 message들은 message map을 이용하여 해당 message handler를 호출 Message Handling (cont’d) BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { // special case for commands if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) { lResult = 1; goto LReturnTrue; } return FALSE; } // special case for notifies if (message == WM_NOTIFY) { } // special case for activation if (message == WM_ACTIVATE) { } // special case for set cursor HTERROR if (message == WM_SETCURSOR ) { } Message Handling (cont’d) const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap(); UINT iHash; iHash = (LOWORD((DWORD)pMessageMap) ^ message) & (iHashMax-1); AfxLockGlobals(CRIT_WINMSGCACHE); AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash]; const AFX_MSGMAP_ENTRY* lpEntry; if (message == pMsgCache->nMsg && pMessageMap == pMsgCache>pMessageMap) { … } else { // not in cache, look for it pMsgCache->nMsg = message; pMsgCache->pMessageMap = pMessageMap; for (/* pMessageMap already init'ed */; pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap) { AfxFindMessageEntry(); } Message Handling Trace 두가지 종류의 message Regular window message Command message WM_MOUSEMOVE 메뉴나 control이 보내는 message WM_COMMAND 서로 다른 방식으로 처리 CWnd::OnWndMsg()참고 Handling WM_COMMAND 가정 CWnd::OnWndMsg()함수에서 Main frame의 메뉴 명령 수행 CWnd::OnCommand()함수 호출 CWnd::OnCommand() Virtual function(Framework이 적절한 version의 함수를 호출함) CFrameWnd::OnCommand() On-line help에 관한 것이면 해당 기능 수행 아니면 CWnd::OnCommand()함수 호출 Handling WM_COMMAND (cont’d) “WinFrm.cpp” BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam) { HWND hWndCtrl = (HWND)lParam; UINT nID = LOWORD(wParam); CFrameWnd* pFrameWnd = GetTopLevelFrame(); ASSERT_VALID(pFrameWnd); if (pFrameWnd->m_bHelpMode&&hWndCtrl==NULL&&nID!=ID_HELP&& nID!= ID_DEFAULT_HELP && nID != ID_CONTEXT_HELP) { // route as help if (!SendMessage(WM_COMMANDHELP, 0, HID_BASE_COMMAND+nID)) SendMessage(WM_COMMAND, ID_DEFAULT_HELP); return TRUE; } // route as normal command return CWnd::OnCommand(wParam, lParam); } Handling WM_COMMAND (cont’d) “WinCore.Cpp” BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam) { if (hWndCtrl == NULL) else { if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd) return TRUE; // locked out - ignore control notification // reflect notification to child window control if (ReflectLastMsg(hWndCtrl)) return TRUE; // eaten by child // zero IDs for normal commands are not allowed if (nID == 0) return FALSE; } return OnCmdMsg(nID, nCode, NULL, NULL); } Handling WM_COMMAND (cont’d) CWnd::OnCommand()(WINCORE.CPP) LPARAM을 조사 만약 control이 보낸 message이면 control에게 다시 message를 reflect한 후 return 아니면 CWnd::OnCmdMsg()(virtual function)함수 호출 Handling WM_COMMAND (cont’d) “WinFrm.cpp” BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { CPushRoutingFrame push(this); // pump through current view FIRST CView* pView = GetActiveView(); if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // then pump through frame if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // last but not least, pump through app CWinApp* pApp = AfxGetApp(); if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; } Handling WM_COMMAND (cont’d) “ViewCore.cpp” BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // first pump through pane if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // then pump through document if (m_pDocument != NULL) { // special state for saving view before routing to document CPushRoutingView push(this); return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } return FALSE; } Handling WM_COMMAND (cont’d) “DocCore.cpp” BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise check template if (m_pDocTemplate != NULL && m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; } Handling WM_COMMAND (cont’d) CFrameWnd::OnCmdMsg()(WINFRM.CPP) 다음의 순서로 해당 OnCmdMsg()함수를 호출 Active view Active view’s document Main frame window Application Handling WM_COMMAND (cont’d) 만약 active view에 해당 handler가 있다면 CView::OnCmdMsg()가 호출됨 CView::OnCmdMsg()(VIEWCORE.CPP) View에서 처리를 시도한 후 안되면 document에서 처리를 시도 View에서 처리 시도는 CWnd::OnCmdMsg()함수의 호출로 이루어짐 이 때 CWnd는 OnCmdMsg를 override하지 않기 때문에 CCmdTarget::OnCmdMsg를 호출하는 결과가 됨 Handling WM_COMMAND (cont’d) “CmdTarg.cpp” BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { for (pMessageMap = GetMessageMap(); pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap) { lpEntry = AfxFindMessageEntry (pMessageMap->lpEntries, nMsg, nCode, nID); if (lpEntry != NULL) { return _AfxDispatchCmdMsg(this, nID, nCode, lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo); } } return FALSE; // not handled } Handling WM_COMMAND (cont’d) CCmdTarget::OnCmdMsg()(CMDTARG.CPP) Message map을 보고 message handler를 찾음 찾게 되면 _AfxDispatchCmdMsg()함수를 호출하여 해당 handler를 실행시킴 _AfxDispatchCmdMsg()(CMDTARG.CPP) Function signature에 따라 다른 동작을 수행 Regular command Extended command Visual Basic control 등등 Handling WM_COMMAND (cont’d) Frame window일 때의 처리과정 정리 AfxWndProc() AfxCallWndProc() CWnd::WindowProc() CWnd::OnWndMsg() CFrameWnd::OnCommand() CWnd::OnCommand() CFrameWnd::OnCmdMsg() CCmdTarget::OnCmdMsg() _AfxDispatchCmdMsg() CMainFram::On~() Handling WM_COMMAND (cont’d) Document일 때의 처리과정 정리 AfxWndProc() AfxCallWndProc() CWnd::WindowProc() CWnd::OnWndMsg() CFrameWnd::OnCommand() CWnd::OnCommand() CFrameWnd::OnCmdMsg() CView::OnCmdMsg() Cdocument::OnCmdMsg() CCmdTarget::OnCmdMsg() _AfxDispatchCmdMsg() CMyDoc::On~() Handling WM_COMMAND (cont’d) View일 때의 처리과정 정리 AfxWndProc() AfxCallWndProc() CWnd::WindowProc() CWnd::OnWndMsg() CFrameWnd::OnCommand() CWnd::OnCommand() CFrameWnd::OnCmdMsg() CView::OnCmdMsg() CCmdTarget::OnCmdMsg() _AfxDispatchCmdMsg() CMyView::On~() Handling WM_COMMAND (cont’d) App일 때의 처리과정 정리 AfxWndProc() AfxCallWndProc() CWnd::WindowProc() CWnd::OnWndMsg() CFrameWnd::OnCommand() CCmdTarget::OnCmdMsg() _AfxDispatchCmdMsg() CMyApp::On~() Handling WM_COMMAND (cont’d) Dialog Box일 때의 처리과정 정리 AfxWndProc() AfxCallWndProc() CWnd::WindowProc() CWnd::OnWndMsg() CDialog::OnCmdMsg() CCmdTarget::OnCmdMsg() _AfxDispatchCmdMsg() CAboutDlg::On~() 예 – Clear All Handling 과정 (MDI application) Main frame window가 message를 받는다 현재 활성화된 MDI child window가 처리 할 기회를 얻는다 Child window가 먼저 view에게 처리 기회를 준다. 실패하여 view가 연결된 document가 처리를 시도한다 Document가 메시지 핸들러를 찾는 데 성공한다. Handling Regular Window Message Command message일 때와 처음의 과정은 비슷 AfxWndProc() AfxCallWndProc() WindowProc() OnWndMsg() 이 함수 안에서 message handler를 찾기 위해AfxFindMessageEntry()를 호출 Handling Regular Window Message (cont’d) AfxFindMessageEntry() 두 버전이 있음. Assembly language : intel-based machine C language : otherwise Message map에서 해당 핸들러가 있는지 검색 END_MESSAGE_MAP에 의해 생성된 table의 끝까지 검색 Handling Regular Window Message (cont’d) Command message와의 차이점 Command message는 handler를 찾기 위해서 여러 장소를 옮겨다님 Regular message는 OnWndMsg()에서 handler를 찾으면 바로 해당 handler를 호출하고, 아니면 DefWindowProc()를 이용 Handling Regular Window Message (cont’d) 예) View에서 WM_SIZE message처리 과정 AfxWndProc() AfxCallWndProc() CWnd::WindowProc() CWnd::OnWndMsg() CSdiappView::OnSize() Other Kinds of Messages 지금까지 살펴 본 메시지 종류 WM_COMMAND Window messages(WM_SIZE, WM_MOVE, …) 그 이외의 메시지 종류 WM_NOTIFY WM_ACTIVATE WM_SETCURSOR Other Kinds of Messages (cont’d) WM_NOTIFY Control이 보내는 message 항상 notify message 반면, WM_COMMAND command이거나 notify message Other Kinds of Messages (cont’d) CWnd::OnWndMsg()에서 // special case for notifies if (message == WM_NOTIFY) { NMHDR* pNMHDR = (NMHDR*)lParam; if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult)) goto LReturnTrue; return FALSE; … } struct NMHDR { HWND hwndFrom; UINT idFrom; UINT code; // control that sent notification // ID of control // notification code Other Kinds of Messages (cont’d) “Wincore.cpp” BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult) { NMHDR* pNMHDR = (NMHDR*)lParam; HWND hWndCtrl = pNMHDR->hwndFrom; // get the child ID from the window itself UINT nID = _AfxGetDlgCtrlID(hWndCtrl); int nCode = pNMHDR->code; if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd) return TRUE; // locked out - ignore control notification // reflect notification to child window control if (ReflectLastMsg(hWndCtrl, pResult)) return TRUE; // eaten by child AFX_NOTIFY notify; notify.pResult = pResult; notify.pNMHDR = pNMHDR; return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), &notify, NULL); } Other Kinds of Messages (cont’d) BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult) { CHandleMap* pMap = afxMapHWND(); CWnd* pWnd = (CWnd*)pMap->LookupPermanent(hWndChild); if (pWnd == NULL) { // check if the window is an OLE control … return FALSE; } return pWnd->SendChildNotifyLastMsg(pResult); } Other Kinds of Messages (cont’d) BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); return OnChildNotify(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult); } Other Kinds of Messages (cont’d) Message reflection OnWndMsg()에서 OnNotify()를 호출 OnNofity()는 다시 OnChildNotify()를 호출하여 control로 message를 다시 보냄 결국에는 control이 parent에 의존하지 않고 자신의 일을 처리하게 함 Other Kinds of Messages (cont’d) WM_ACTIVATE OnWndMsg()함수에서 _AfxHandleActivate()를 호출 WM_SETCURSOR OnWndMsg()함수에서 _AfxHandleSetCursor()함수 호출 Speed Message-map Matching 방법 최근에 처리 된 메시지를 캐쉬에 저장 같은 메시지를 다시 처리할 가능성이 높음 장점 Unhandled message 빠르고 효과적으로 처리 Message Loop Hooking Message loop hooking Message가 해당 handler에 의해 처리되기 전에 어떠한 작업을 하고 싶을때 PreTranslateMessage() CWinApp::PreTranslateMessage() CWinApp::Run()함수는 message가 message pump에 의해 처리되기 전에 위 함수를 호출 TRUE를 return하면 message pump는 해당 message에 관해서는 처리를 하지 않음 CWnd::PreTranslateMessage() Message Loop Hooking (cont’d) “THRDCORE.cpp” int CWinThread::Run() { // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } Message Loop Hooking (cont’d) // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable } Message Loop Hooking (cont’d) BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) { } // process this message if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); } return TRUE; } Message Loop Hooking (cont’d) BOOL CWinThread::PreTranslateMessage(MSG* pMsg) { // if this is a thread-message, short-circuit this function if (pMsg->hwnd == NULL && DispatchThreadMessageEx(pMsg)) return TRUE; // walk from target to main window CWnd* pMainWnd = AfxGetMainWnd(); if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg)) return TRUE; // in case of modeless dialogs, last chance route through main // window's accelerator table if (pMainWnd != NULL) { CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd); if (pWnd->GetTopLevelParent() != pMainWnd) return pMainWnd->PreTranslateMessage(pMsg); } return FALSE; // no special processing } Message Loop Hooking (cont’d) “WinCore.Cpp” BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg) { // walk from the target window up to the hWndStop window checking // if any window wants to translate this message for (HWND hWnd=pMsg->hwnd; hWnd != NULL;hWnd=::GetParent(hWnd)) { CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); if (pWnd != NULL) { // target window is a C++ window if (pWnd->PreTranslateMessage(pMsg)) return TRUE; } // got to hWndStop window without interest if (hWnd == hWndStop) break; } return FALSE; // no special processing }