본문 바로가기

프로젝트/MFC 프로그래밍

1. MFC programming : 쉽게 배우는 MFC 윈도우 프로그래밍

반응형

 MFC를 공부하면서 간단하게 정리하는 방식으로 글을 올릴 것이다. 해당 글은 쉬게 배우는 MFC 윈도우 프로그래밍(한빛아카데미 출판, 김선우, 신화선 지음)의 Chapter 1부터 Chapter 3까지를 정리한 것이다.

 

 앞서서 윈도우 프로그래밍은 간단하게 윈도우 운영체제에서 구동되는 윈도우 프로그래밍을 만드는 것이다. 윈도우 운영체제의 특징으로는 다음과 같은 것을 들 수 있다.

 

  • 그래픽 사용자 인터페이스 : 사용자의 인터페이스가 DOS 방식이 아닌, 그래픽 기반의 인터페이스
  • 메시지 구동 구조 : 운영체제가 프로그램의 외부 또는 내부에 변화가 발생했음을 메시지를 통해 해당 프로그램에 알려준다. 프로그램은 사용자가 지정한 논리의 흐름에 따라 이루어지는 거이 아닌, 메시지에 대해 매순간 어떻게 반응할 것인지를 기준으로 동작한다. (이벤트 발생 및 대응되는 메시지 발생 - > 운영 체제가 관리하는 시스템 메시지 큐에 정보 저장 -> 각각의 응용 프로그램은 운영체제로부터 독립적인 메시지 큐를 할당 -> 운영체제가 적당한 메시지를 응용프로그램의 메시지 큐에 전달 -> 응용 프로그램은 자신의 메시지 큐를 감시하다 필요시 구동, 나머지는 대기 상태)
  • 멀티 태스킹과 멀티 스레딩의 지원 : 멀티 태스킹(CPU가 적절하게 시분할하여, 여러 프로세스를 동시에 처리하는 것처럼 보이게 만드는 것), 멀티 스레딩(하나의 응용 프로그램에 내에서도 여러 프로시저를 동시에 처리하는 것처럼 만드는 것, 역시 CPU의 시분할과 연관되어 있음)

 

 다음은 윈도우 응용 프로그램의 특징이다.

 

  • API 호출문의 집합 : 윈도우 응용 프로그래밍 = 윈도우 API를 많이 호출하여 만든 것, MFC는 그 윈도우 API를 모아 C++ 라이브러리 형태로 제공하는 것
  • 메시지 핸들러의 집합 : 메시지에 반응하여 동작을 결정하는 메시지 핸들러의 집합, 이 집합을 윈도우 프로시저라 부름
  • 실행 파일과 DLL의 집합 : DLL(Dynamic Linked Library, 프로그램 실행 중에 결합하여 사용할 수 있는 코드와 리소스의 집합, C++의 라이브러리와 기능은 비슷하나, 일반적인 라이브러리는 컴파일 중 모두 컴파일되나, DLL은 각각 컴파일하고 필요할 때마다 참조하는 방식임, 비유하자면 일반 라이브러리는 병사가 직접 들고 가는 전투식량이라 하면, DLL은 창고에 보관된 식량 자원임), 윈도우 API는 DLL의 형태로 제공된다. (윈도우 응용 프로그램 = 윈도우 API + 사용자 정의 코드 + 리소스)
  • 장치 독립성 : Device Driver만 설치 되어 있다면 하드웨어 종류를 신경쓰지 않고 API를 통해 구동 가능(어댑터의 역할)

 

 다음은 가장 기본적인 윈도우 프로그래밍의 틀이라 할 수 있겠다. C에서 제공하는 라이브러리를 이용하여 빈창을 띠우는 코드이다.

 

// MyWin32.cpp : 빈 창을 띄우는 예제
//

#include "stdafx.h"
#include "MyWin32.h"

//윈도우 프로시저
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//메시지의 종류에 따라 적절하게 처리한다
	if (uMsg == WM_DESTROY) PostQuitMessage(0);

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//메인 함수
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	//윈도우 클래스를 초기화하고 운영체제에 등록(종류에 대한 등록, 같은 프로그램을 여러개 띄어 놓았을 경우
    프로세스는 다를 지언정, 윈도우 클래스는 같다)
    WNDCLASS wc;

	wchar_t my_class_name[] = L"tipssoft";
	wc.cbClsExtra = NULL; //여분 메모리
	wc.cbWndExtra = NULL; //여분 메모리
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW; //배경색 지정
	wc.hCursor = LoadCursor(NULL, IDC_ARROW); //커서 모양 지정
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); //아이콘 모양
	wc.hInstance = hInstance; //인스턴스 핸들
	wc.lpfnWndProc = WndProc; //윈도우 프로시저
	wc.lpszClassName = my_class_name; //클래스 이름
	wc.lpszMenuName = NULL; //메뉴 설정
	wc.style = CS_HREDRAW | CS_VREDRAW; //스타일 설정
	
    //윈도우 클래스를 등록한다
	RegisterClass(&wc);
	
    //윈도우를 생성하고 화면에 나타낸다
	HWND hWnd = CreateWindow(my_class_name, L"www.tipssoft.com",
		WS_OVERLAPPEDWINDOW, 100, 90, 400, 350, NULL, NULL, hInstance, NULL);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	
    //메시지 큐에서 메시를 하나씩 꺼내 처리한다.
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}

 

 이제 Visual Stuido에서 클래스 형태로 윈도우 프로그래밍을 하는 법에 대해서 알아보자. 기본적인 원리는 같다.

 

#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); //윈도우 띄우기
 m_pMainWnd->UpdateWindow(); //윈도우 업데이트
 return TRUE;
}

CMainFrame::CMainFrame()
{
 Create(NULL, "HelloMFC Application"); //메인 클래스 정의(윈도우 클래스 네임, 윈도우의 네임)
}

//명령어에 대한 함수들
void CMainFrame::OnPaint()
{
 char *msg = "Hello, MFC";
 CPaintDC dc(this); //BeginPaint, EndPaint 합한 버전
 dc.TextOut(100, 100, msg, lstrlen(msg));
}

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

// 메시지 맵
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
 ON_WM_PAINT()
 ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

 

 다음은 MFC를 이용한 콘솔 프로그램이다. 코드는 다음과 같다.

//console.cpp
#include "pch.h"
#include "framework.h"
#include "Console.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


CWinApp theApp; //가장 기본적인 윈도우 클래스, 다만 GUI를 다루는 것이 아니라서 별도 선언 안 함

using namespace std;

//시작점(WinMain과 같은 역할)
int main() {
    int nRetCode = 0;

    HMODULE hModule = ::GetModuleHandle(nullptr);

    //AfxWinInit() : MFC를 사용하기 위한 초기화 함수
    if (hModule != nullptr) {
        if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0)) {
            wprintf(L"심각한 오류 : MFC 초기화 실패\n");
            nRetCode = 1;
        }
        else {
            CString str; //객체 만들기
            str.LoadString(IDS_APP_TITLE); //리소스로 정의된 문자열 로드
            _tprintf(_T("Hello from %s!\n"), (LPCTSTR)str); //printf
            getchar();
        }
    }
    else {
        wprintf(L"심각한 오류 : GetModuleHandle 실패\n");
        nRetCode = 1;
    }

    return nRetCode;
}

 

 유틸리티 클래스는 윈도우 응용 프로그램에서 자수 사용되는 데이터 타입이나 기능을 좀 더 편리하게 사용할 수 있도록 MFC에서 제공하는 크래스를 총칭하는 용어다. 유의 사항은 MFC를 이용하여 프로그램을 작성하더라도 윈도우 API에서 제공되는 데이터 타입을 그대로 사용하는 경우도 많다는 것이다. 다음은 자주 사용되는 데이터 타입이다.

 

  • BOOL/BOOLEAN : bool
  • BYTE, WORD, DWORD, LONG : 8bit, 16bit, 32bit, 32bit, LONG을 제외하고 unsigned type
  • U* : unsigned*
  • H* : 핸들(*), 윈도우는 기본적으로 응용 프로그램이 직접 시스템 자원을 활용하는 것을 보안상 허락하지 않는다.
  • P*/LP* : *에 대한 포인터
  • PSTR, PCSTR : ANSI 문자열
  • PTSTR, PCTSTR : ANSI 또는 Unicode 문자열
  • COLOREF : 32bit RGB 색상값
  • POINT : 점의 x, y 좌표 (LONG, LONG)
  • RECT : 직사각형의 왼쪽 상단과 오른쪽 한단 좌표 (LONG, LONG, LONG, LONG)
  • SIZE : 폭과 높이 (LONG, LONG)

 

 MFC에서 기본적으로 사용되는 유틸리티 클래스에 대해서 알아보고자 한다.

 

  • CString : String 클래스와 역할은 비슷, 다만  String 클래스에 비해 사이즈가 작고, MFC 함수와 연동되어 사용할 수 있어서 좋음, 특징 : ANSI, 유니코드의 지원, 가변 길이 문자열 지원, const TCHAR* or LPCTSTR 대신 CString 객체 직접 사용 가능: str.Format("", ~) : printf와 사용법은 유사(사용자 지정 형식에 맞는 문자열 넣음), LoadString() : 문자열 리소스를 받음
  • CPoint, CRect, CSize : 구조체 타입인 POINT, RECT, SIZE에서 파생된 클래스, 상속 관계여서 CPoint, CRect, CSize는 각 원형 Struct와 호환성이 있다. 기본적으로 클래스들은 연산자 ==, !=, + , -, +=, -=를 제공하며, 부가적으로 클래스 고유의 메서드를 가지고 있다.
  • CTime, CTimeSpan : 각각 절대적인 시간과 시간의 차이를 다루는 클래스 이며, 내부적으로 시간 값을 65bit로 저장한다. CTime::Format()은 앞에서도 보았던 것처럼 시간을 사용자가 원하는 형태의 문자열로 반환해 준다. CTimeSpan은 - 연산을 지원하며, Get 또는 Format 함수로 출력이 가능하다.
  • Sleep() : 일정 시간동안 잠시 프로그램을 대기시키는 역할, 멀티 테스킹 관점에서 보았을 때, 우선순위가 그 다음으로 높은 프로그램에 잠시 양도를 해줌으로써 효율성을 높이는 역할을 한다. 멀티 스레딩에서도 통하는 개념

 다음은 MFC에서 지원하는 집합 클래스 이다.

  • CArray : 배열 클래스 : 템플릿을 지원한다! 메서드 종류(SetSize, InsertAt, RemoveAt,  ...)
  • CList : 리스트 클래스 : 템플릿을 지원, 메서드(AddHead, AddTail, GetNext(H->T), GetPrev(T->H), InsertAfter, InsertBefore, RemoveAfter, RemoveBefore... )
  • CMap :  맵 클래스(key - data, 이 때 key는 해시 함수를 걸쳐서 저장 위치를 알려준다) 메서드(LookUp(), GetNextAssoc(pos, key 저장할 변수, value 저장할 변수), SetAt(key, data), RemoveKey(key), InitHashTable()//최적화), 해시 함수는 적절하게 제공되어야 한다.
//만약 해시 함수를 지원하지 않는 경우 별도로 지원할 필요가 있음
template <> UINT AFXAPI HashKey(){
	//해쉬 함수의 정의
}

//Visual C++ 2019에서 HashKey 정의
template <class ARG_KEY> 
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key){
	ldiv_t HashVal = ldiv((LONG)(ARG_KEY)key, 127773);
    HashVal.rem = 16807 * HashVal.rem - 2836*HashVal.quot;
    if(HashVal.rem < 0)
    	HashVal.rem += 2147483647;
     return ((UINT)HashVal.rem)
}

 

 MFC는 MS에서 윈도우 응용 프로그램 개발을 위해 만든 C++ 클래스 라이브러리이다. 다음은 MFC의 철학이다.

 

  • 응용 프로그램 작성에 수고를 덜어줄 것
  • API 기반 SDK 프로그램과 대등한 속도
  • 코드 크기의 최소화
  • 경우에 따라서는 API 함수를 직접 호출할 수 있을 것
  • C -> C++ 에 용이할 것
  • SDK 프로그래밍에 대한 기반 지식을 재활용
  • 기존 C 언어에 비해 API를 편하게 사용할 수 있도록 할 것 + 간단한 구현

 

 MFC를 구성하는 요소로 MFC 클래스, 매크로, 전역 변수, 전역 함수 정도로 나눌 수 있다. 당연히 주의깊게 봐야 할 부분은 MFC 클래스 이다. CObjectClass는 MFC 클래스의 최상위 클래스이다. 다음은 CObjectClass의 서비스이다.

 

  • 실행 시간 클래스 정보 : 프로그램 실행 중 객체 정보를 알아낸다
  • 동적 객체 생성 : 객체를 동적으로 생산
  • 직렬화 : 객체를 저장, 혹은 읽기
  • 타당성 점검 : 객체 상태 점검
  • 집합 클래스와의 호환성 : 서로 다른 클래스 객체르르 집합 클래스에 저장할 수 있음

 

 전역 함수는 어디서든 호출해서 사용할 수 있는 함수를 의미한다. (Afx~ : Application Frameworks라는 뜻) 

 MFC 응용 프로그램 구조 : 프레임 윈도우, 안에 뷰가 있고, 그 뷰를 포함한 사용자가 조작하고 보는 부분을 클라이언트 영역이라고 함/ 가장 바깥 쪽의 윈도우인 프레임 윈도우와 내부 윈도우는 부모 관계임

 

응용프로그램 클래스 : 프레임 윈도우 생성, 메시지 루프 제공 > 프레임 윈도우 클래스 : 프레임 윈도우 기능 제공, 뷰 생성 > 뷰클래스 : 뷰 기능 제공

 

// MFCApp.cpp : Defines the class behaviors for the application.
//

#include "pch.h"
#include "framework.h"
#include "afxwinappex.h"
#include "afxdialogex.h"
#include "MFCApp.h"
#include "MainFrm.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CMFCAppApp

BEGIN_MESSAGE_MAP(CMFCAppApp, CWinApp)
	ON_COMMAND(ID_APP_ABOUT, &CMFCAppApp::OnAppAbout)
END_MESSAGE_MAP()


// CMFCAppApp construction

CMFCAppApp::CMFCAppApp() noexcept
{

	// TODO: replace application ID string below with unique ID string; recommended
	// format for string is CompanyName.ProductName.SubProduct.VersionInformation
	SetAppID(_T("MFCApp.AppID.NoVersion"));

	// TODO: add construction code here,
	// Place all significant initialization in InitInstance
}

// The one and only CMFCAppApp object

CMFCAppApp theApp;


// CMFCAppApp initialization

BOOL CMFCAppApp::InitInstance()
{
	CWinApp::InitInstance();


	EnableTaskbarInteraction(FALSE);

	// AfxInitRichEdit2() is required to use RichEdit control
	// AfxInitRichEdit2();

	// Standard initialization
	// If you are not using these features and wish to reduce the size
	// of your final executable, you should remove from the following
	// the specific initialization routines you do not need
	// Change the registry key under which our settings are stored
	// TODO: You should modify this string to be something appropriate
	// such as the name of your company or organization
	SetRegistryKey(_T("Local AppWizard-Generated Applications"));


	// To create the main window, this code creates a new frame window
	// object and then sets it as the application's main window object
	CFrameWnd* pFrame = new CMainFrame;
	if (!pFrame)
		return FALSE;
	m_pMainWnd = pFrame;
	// create and load the frame with its resources
	pFrame->LoadFrame(IDR_MAINFRAME,
		WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, nullptr,
		nullptr);





	// The one and only window has been initialized, so show and update it
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

int CMFCAppApp::ExitInstance()
{
	//TODO: handle additional resources you may have added
	return CWinApp::ExitInstance();
}

// CMFCAppApp message handlers


// CAboutDlg dialog used for App About

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg() noexcept;

// Dialog Data
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() noexcept : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()

// App command to run the dialog
void CMFCAppApp::OnAppAbout()
{
	CAboutDlg aboutDlg;
	aboutDlg.DoModal();
}

// CMFCAppApp message handlers

 

 일단 가장 기본적인 코드는 이런 형태이다. 클래스에 대한 정리는 차후에 할 예정이다.

반응형