C, C++ Language/MFC Window Programming

🔥Ch01. Window Programming Fundamentals🔥(3/3) - MFC

metamong 2025. 10. 8.

01. MFC 기본 구조

① 응용 프로그램 클래스 정의

② 메인(프레임) 윈도우 클래스 정의
(메인 윈도우와 프레임 윈도우는 엄밀히 다름. 이후 차이점 설명 예정)

③ 응용 프로그램 객체 선언.

④ 메시지 맵 선언.

※ MFC 프로그램 특징 요약

(1) WinMain() 함수 존재 x. 즉 프로그램 실행 시작점이 눈에 보이지 않는다.

 

(2) 사용자가 함수를 직접 호출하기 보다 MFC 내부에 숨겨진 코드에서 사용자가 정의한 함수를 호출하는 경우가 많다. 가상 함수가 여기에 속한다.

 

(3) 각 메시지에 대한 처리 코드를 함수 단위(message handler)로 따로 만든다. 해당 message와 message handler를 연결하기 위해 message map macro를 사용한다.

01-1. MFC 예제

: Windows 데스크톱 마법사 - 솔루션 및 프로젝트를 같은 디렉터리에 배치 - 데스크톱 애플리케이션(.exe) / 빈 프로젝트 체크

※ '고급' 항목의 MFC 사용 속성을 '공유 DLL에서 MFC 사용'을 선택한다. MFC가 제공하는 기능을 사용하려면 MFC 라이브러리와 링크해야 하는데, 생성 코드의 크기를 줄이기 위해 대개 DLL 버전 사용.

: 코드

#include <afxwin.h>

// 응용 프로그램 클래스를 선언한다.
class CHelloApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

// 메인 윈도우 클래스를 선언한다.
class CMainFrame : public CFrameWnd
{
public:
	CMainFrame();

protected:
	afx_msg void OnPaint();
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	DECLARE_MESSAGE_MAP()
};

// 응용 프로그램 객체를 선언한다.
CHelloApp theApp;

// 응용 프로그램 클래스를 정의한다.
BOOL CHelloApp::InitInstance()
{
	m_pMainWnd = new CMainFrame;
	m_pMainWnd->ShowWindow(m_nCmdShow);
	return TRUE;
}

// 메인 윈도우 클래스를 정의한다.
CMainFrame::CMainFrame()
{
	Create(NULL, _T("HelloMFC"));
}

void CMainFrame::OnPaint()
{
	CPaintDC dc(this);
	const TCHAR* msg = _T("Hello, MFC");
	dc.TextOut(100, 100, msg, lstrlen(msg));
}

void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
{
	MessageBox(_T("마우스 클릭!"), _T("마우스 메시지"));
}

// 메시지 맵을 선언한다.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

01-2. MFC 예제 코드 분석

① Header File

#include <afxwin.h>

 

: <afxwin.h>는 여러 헤더 파일을 포함하는 헤더 파일. MFC에서 제공하는 각종 클래스 정의, 데이터 타입, 매크로 등도 선언. SDK 프로그램에서 소개한 windows.h 파일도 물론 포함.

 

② Class Delcaration

// 응용 프로그램 클래스를 선언한다.
class CHelloApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

// 메인 윈도우 클래스를 선언한다.
class CMainFrame : public CFrameWnd
{
public:
	CMainFrame();

protected:
	afx_msg void OnPaint();
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	DECLARE_MESSAGE_MAP()
};

 

: MFC는 SDK 프로그램의 WinMain() 함수의 기능과 Window Procedure 기능을 적절히 분할하여 '2개의 클래스'로 제공한다.

 

[1] CWinApp 클래스) 응용 프로그램의 초기화 코드, 메시지 루프, 종료 코드를 제공

 

[2] CFrameWnd 클래스) 메인 윈도우의 기능 제공.

 

: 대부분의 MFC 프로그램은 CWinApp과 CFrameWnd 클래스를 그대로 사용하지 않고 C++의 상속 기능을 이용해 새로운 클래스를 만들고 일부 기능을 재정의해서 사용한다.

 

③ 응용 프로그램 객체

// 응용 프로그램 객체를 선언한다.
CHelloApp theApp;

 

: 대부분의 MFC 응용 프로그램은 CWinApp 클래스를 상속받아 클래스를 만들고, 이 클래스로부터 객체 하나를 전역 변수로 생성. 이 객체를 '응용 프로그램 객체(Application Object)'. 전역 객체가 오직 한 개만 있어야 한다는 점만 지키면 된다.

 

: SDK 프로그램과 달리 MFC 프로그램에서는 WinMain() 함수를 작성하지 않고 MFC 라이브러리에서 내부적으로 제공한다. 프로그램이 일단 시작되면 WinMain() 함수는 응용 프로그램 객체의 주소를 알아내 여기에 속한 멤버 함수를 내부적으로 정한 순서에 따라 차례로 호출한다.

 

④ Class 정의

// 응용 프로그램 클래스를 정의한다.
BOOL CHelloApp::InitInstance()
{
	m_pMainWnd = new CMainFrame;
	m_pMainWnd->ShowWindow(m_nCmdShow);
	return TRUE;
}

// 메인 윈도우 클래스를 정의한다.
CMainFrame::CMainFrame()
{
	Create(NULL, _T("HelloMFC"));
}

void CMainFrame::OnPaint()
{
	CPaintDC dc(this);
	const TCHAR* msg = _T("Hello, MFC");
	dc.TextOut(100, 100, msg, lstrlen(msg));
}

void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
{
	MessageBox(_T("마우스 클릭!"), _T("마우스 메시지"));
}

 

[1] InitInstance(): MFC 내부에 숨겨진 WinMain() 함수는 프로그램이 시작되면 응용 프로그램 객체의 InitInstance() 함수를 호출한다. 이 함수는 프로그램 실행 초기에 호출되므로 각종 초기화 작업을 하기 좋은 위치. 해당 함수 재정의하지 않으면, 특별히 하는 일이 없기에 반드시 재정의 필수.

(virtual인 가상함수로 선언에 주의. 가상함수로 재정의를 하였기에 CHellpApp::InitInstance() 함수가 호출된다)

 

: 해당 재정의 함수 내에서 메인 윈도우의 기능을 가지는 CMainFrame 객체를 동적으로 생성하고, ShowWindow() 멤버 함수를 호출하여 윈도우를 화면에 나타낸다(m_nCmdShow는 initial state of the application's window). SDK와 달리 첫번째 인자 window handle이 숨겨짐. MFC가 내부에 숨겨둔 window handle을 이용하여 API 함수를 호출하기 때문.

 

: 초기화 작업이 성공적으로 끝났으면 반드시 TRUE 리턴. FALSE 리턴하면 더 진행하지 않고 프로그램 종료.

 

(ExitInstance()는 프로그램 종료 시 처리할 작업이 있다면, 해당 함수를 재정의하고 필요한 코드 작성하면 됨)

 

[2] CMainFrame() 생성자: Create() 함수를 이용하여 윈도우 생성. 첫번째 인자는 윈도우 클래스 이름으로 NULL 전달하면 MFC 내부적으로 등록된 윈도우 클래스가 사용된다. / 두번째 인자는 생성되는 Window의 타이틀 바에 표시될 윈도우 이름.

(CMainFrame() 객체를 만들더라도, 생성자에 CFrameWnd::Create() 함수를 반드시 호출 필요. 그래야 OS 수준의 실제 윈도우 생성)

 

[3] OnPaint() / OnLButtonDown(): 각각 WM_PAINT, WM_LBUTTONDOWN 메시지를 처리하는 역할 진행.

: switch-case 문을 이용해 처리하는 SDK 프로그램과 달리 MFC에서는 메시지별로 함수를 따로 만들어 처리.

: SDK 프로그램은 window procedure가 모든 메시지 처리 코드를 담고 있다. / MFC 프로그램은 메시지 처리 코드가 각각의 멤버 함수(message handler)로 분리되어 있다.

 

: OnPaint()는 window의 client 영역에 데이터 출력. SDK와 같이 BeginPaint()/EndPaint() 호출이 꼭 필요한데, CPaintDC 객체를 만들면 생성자가 호출되고, MFC 제공 생성자 코드에서 BeginPaint() 자동 호출. 소멸자 호출 시점에 EndPaint() 자동 호출.

 

: OnLButtonDown()은 WM_LBUTTONDOWN 메시지 처리.

 

[실행 순서]

① CHelloApp the App 생성

② MFC내부 WinMain() 함수 실행

③ CHelloApp::InitInstance() 찾아서 호출

④ InitInstance() 내의 내용: CMainFrame 생성자 호출로 m_pMainWnd 객체 동적 생성.

⑤ CMainFrame 생성자 호출

⑥ CMainFrame 생성자 내의 Create() 호출로 윈도우 생성

⑦ InitInstance() 내의 두번째 줄 ShowWindow() 호출로 창 화면에 띄워짐

⑧ 메시지 루프 시작

⑨ 메시지 루프 시작으로 WM_PAINT -> OnPaint() 호출

⑩ (선택) 사용자 클릭 시 WM_LBUTTON_DOWN -> OnLButtonDown() 호출

 

⑤ Message Map

// 메시지 맵을 선언한다.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

 

: MFC는 message map이라는 방식을 통해 윈도우 메시지와 해당 message handler를 연결한다. ON_WM_PAINT() 매크로는 WM_PAINT() 메시지와 OnPaint() 함수를 연결하고, ON_WM_LBUTTONDOWN() 매크로는 WM_LBUTTONDOWN 메시지와 OnLButtonDown() 함수를 연결한다.

 

: 메시지 종류만 선택하면 C++ 툴이 자동으로 매크로 추가.

02. SDK와 MFC 구조 비교

★ SDK는 응용 프로그램이 제공하는 WinMain() 함수에서 직접 실행 시작. 윈도우 메시지가 발생할 때마다 OS 내부 코드에서 응용 프로그램 코드를 호출하여 메시지를 처리하는 구조.

 

★ MFC 프로그램은 MFC 라이브러리가 제공하는 WinMain() 함수에서 실행이 시작되고, 윈도우 메시지가 발생할 때마다 MFC 라이브러리 내부 코드에서 응용 프로그램 코드를 호출하여 메시지를 처리하는 구조.

03. Exercises

Ch01 - Q1. HelloMFC 예제에서 타이틀바 내 글자가 'HelloMFC'에서 'Hello World'가 되도록 수정하세요.

CMainFrame::CMainFrame()
{
	Create(NULL, _T("HelloMFC"));
}

 

Ch01-Q2. HelloMFC 예제에서 윈도우 스타일이 오버랩된 윈도우(Overlapped Window) 대신 타이틀바(Caption)가 있는 팝업 윈도우(Popup Window)가 되도록 수정하시오. 단, 윈도우 위치는 (0,0), 크기는 (500,200)이 되도록 한다.

// 메인 윈도우 클래스를 정의한다.
CMainFrame::CMainFrame()
{
	Create(NULL, _T("HelloMFC"), WS_POPUP | WS_CAPTION, CRect(0, 0, 500, 200));
}

 

 

 

 

 

 

 

 

 

댓글