Windows의 MFC란 무엇인가 ── 기존 자산을 유지보수하기 위한 기초 지식

· 업데이트: · · Windows, MFC, VisualC++, Cpp, Win32, NativeApp, DesktopApp, LegacyCode, 기존자산활용

수정 이력(4건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
기사 서두에 「이 기사의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건) 대응으로 본문을 업데이트했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
예상 독자와 전제 지식, 목적별로 읽을 장 표를 추가했습니다. 메시지 전달 그림(`switch`가 보이지 않는 이유는 자기 클래스에서 기본 클래스로 메시지 맵을 따라가기 때문)과 프레임·뷰·문서의 관계도를 추가하고, Visual Studio Installer 조작 절차와 컴포넌트 ID, 부족 시 오류 예를 보강했습니다. 함정을 증상과 확인 위치 표로 정리했습니다.
최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635320)

이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.

Go Komura (2026). 「Windows의 MFC란 무엇인가 ── 기존 자산을 유지보수하기 위한 기초 지식」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635320 https://comcomponent.com/ko/blog/windows-mfc-overview/

DOI(최신 버전)
10.5281/zenodo.21635320
DOI(이 버전)
10.5281/zenodo.21635321

1. 먼저 잡아 두어야 할 것

Windows의 오래된 데스크톱 애플리케이션을 유지보수하다 보면, 이런 이름을 만나게 됩니다.

CWinApp
CWnd
CDialog
CDialogEx
CFrameWnd
CDocument
CView
CString
CFile
CArchive
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_BN_CLICKED
DoDataExchange
UpdateData

이들은 MFC라고 부르는 C++용 Windows 애플리케이션 프레임워크에서 자주 등장하는 것입니다. MFC는 Microsoft Foundation Classes의 약자로, Win32 API를 C++ 클래스로 다루기 쉽게 하기 위한 라이브러리입니다.

지금의 Windows 앱 개발에는 WinUI, WPF, Windows Forms, Electron, Qt, Web 기술 등 다양한 선택지가 있어, MFC를 신규 개발의 1순위 후보로 다루는 경우는 줄어들었습니다. 그렇다고 사라진 기술도 아닙니다. 업무 애플리케이션, 계측 기기, 제어 소프트웨어, CAD/CAM, 사내 도구, 오래전부터 있는 패키지 소프트웨어 등에서는 지금도 MFC 코드베이스를 유지보수하는 경우가 있습니다.

MFC를 이해하는 데 갖고 있어야 할 관점을 먼저 적습니다.

MFC는 오래된 Windows 데스크톱 앱을 읽기 위한 중요한 지도이다
MFC는 Win32 API를 숨기는 것이 아니라, C++답게 감싼 것으로 본다
MFC 관례를 모르면, 코드 겉모습보다 동작을 잘못 읽기 쉽다
신규 채택보다 기존 자산의 유지보수·연장·단계적 이행에서 가치가 난다

이 기사에서는 MFC의 개요, 애플리케이션 구조, 메시지 맵, Document/View, 대화 상자, DDX/DDV, 리소스, 빌드, 유지보수 시 주의점을 정리합니다.

대상 독자는 C++는 쓸 수 있지만 Win32 API나 MFC는 경험하지 않은 분입니다. 전제 지식의 기준은 이 정도면 충분합니다.

분야 기대하는 수준
C++ 클래스, 상속, 가상 함수, 포인터와 참조를 읽을 수 있다
Win32 API 미경험이어도 됩니다. 창과 메시지라는 말을 들어 본 정도
Visual Studio 솔루션을 열어 빌드해 본 적이 있다
COM / OLE 미경험이어도 됩니다. 27장에서 필요해졌을 때 조사하면 충분합니다

2장 끝에 「MFC를 읽기 위해 필요한 지식」을 적었지만, 그것을 먼저 모두 갖출 필요는 없습니다. 읽으면서 필요해진 시점에 돌아오는 읽기 방식으로 충분합니다.

읽는 순서도 1장부터 41장까지 통독할 필요는 없습니다. 목적별 최단 경로를 적습니다.

목적 읽을 장
MFC가 무엇인지, 지금 어떤 위치인지만 알고 싶다 2〜4장, 36장
기존 MFC 코드를 읽을 수 있게 되고 싶다 6장 → 9장 → 10장 → 14장 → 12장·13장 → 38장
빌드가 되지 않는 것을 고치고 싶다 5장, 25장, 32장, 33장
결함을 조사하고 싶다 34장 → 35장 → 8장·22장
유지보수 방침이나 이행 방침을 정하고 싶다 30장, 31장, 37장, 39장

먼저 잡아 두어야 할 것은 메시지 맵(9장)과 Document/View(14장) 두 가지입니다. 이 둘이 이해되면 「호출자를 찾을 수 없는 함수가 왜 실행되는가」와 「데이터와 화면이 어떻게 연결되어 있는가」를 읽을 수 있게 됩니다. 반대로 이 둘을 건너뛰면 다른 장의 설명이 맥락을 잃습니다.

참고로 이 기사에 등장하는 코드 조각은 장별로 파일에 정리한 참조용 코드집으로 GitHub에 공개하고 있습니다.

windows-mfc-overview - komurasoft-blog-samples (GitHub)

이 글의 지식 맵

MFC는 Win32 API를 C++ 클래스로 다루기 쉽게 만드는 프레임워크로, CWinApp가 앱 전체의 초기화를 담당하고, 메시지 맵이 Windows 메시지를 핸들러 함수에 연결하며, Document/View 아키텍처가 데이터와 UI 표시를 분리하고, DDX/DDV가 대화 상자의 컨트롤과 멤버 변수를 대응시켜 검증합니다. MFC를 쓰려면 Visual Studio Installer에서 MFC 구성 요소를 따로 설치해야 하며, 공유 DLL인지 정적 링크인지도 구성할 수 있습니다. 이 글은 완전 신규의 일반적인 GUI 애플리케이션에서는 MFC 채택을 신중히 판단해야 하는 한편, 기존 MFC 앱의 유지보수·기능 추가·단계적 마이그레이션에서는 현실적으로 가치가 있는 경우가 많다는 자리매김을 보여 줍니다.

MFC를 이용한 Windows 데스크톱 앱 유지보수의 지식 맵MFC가 Win32 API를 C++로 래핑하고 메시지 맵이나 Document/View 등의 메커니즘을 구현한다는 점, 기존 자산의 유지보수에는 맞지만 신규 GUI 개발에는 신중해야 한다는 자리매김, 빌드 환경과 COM 연동에서 주의할 점의 관계를 보여주는 그림.이용한다이용한다자동화한다구현을 담당한다전제로 한다구현을 담당한다전제로 한다이용한다구현을 담당한다전제로 한다이용한다전제로 한다에서 구성할 수 있다전제로 한다이용한다전제로 한다전제로 한다사용은 비권장권장되는 대응전제로 한다MFC(Microsoft Foundation Classes)Win32 APICWinApp메시지 맵Document/View 아키텍처DDX/DDV리소스 파일(.rc/resource.h)CStringVisual Studio Installer의 MFC 구성 요소MFC 링크 방식(Use of MFC)MFC 모듈 상태ActiveX비트수 일치 요건고 DPI 지원완전 신규의 일반적인 GUI 앱 개발기존 MFC 앱의 유지보수·단계적 이행

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. MFC란 무엇인가

MFC는 Windows 네이티브 데스크톱 애플리케이션을 C++로 만들기 위한 클래스 라이브러리입니다.

Win32 API를 직접 쓰는 경우, 전형적으로는 이런 코드를 작성합니다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
        // 그리기 처리
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Win32 API는 매우 강력하지만, C 함수, 핸들, 메시지, 콜백을 중심으로 조립하기 때문에 큰 애플리케이션에서는 가독성이 떨어지기 쉽습니다. MFC는 이를 C++ 클래스로 다룰 수 있게 한 것으로, 창은 CWnd, 대화 상자는 CDialog, 애플리케이션 전체는 CWinApp, 프레임 창은 CFrameWnd, 뷰는 CView와 같은 클래스로 표현됩니다.

class CMainFrame : public CFrameWnd
{
public:
    CMainFrame();

protected:
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    DECLARE_MESSAGE_MAP()
};

MFC는 Win32 API를 완전히 다른 것으로 바꾸는 것이 아니라, Win32 API의 사고방식을 토대로 한, 얇지만 광범위한 C++ 프레임워크입니다. 그래서 MFC를 읽으려면 MFC 클래스뿐 아니라 이쪽 지식도 필요합니다.

Windows 메시지
HWND 등의 핸들
GDI/GDI+
리소스 파일
COM/OLE
DLL과 런타임
문자 코드
스레드와 메시지 루프

「Windows를 몰라도 작성할 수 있는 마법의 라이브러리」라기보다, 「Windows의 구조를 C++ 타입과 프레임워크로 정리한 것」이 MFC의 실상입니다.

3. MFC는 지금도 사용할 수 있는가

MFC는 지금도 Visual Studio에서 사용할 수 있습니다. 다만 자리매김을 오해해서는 안 됩니다. 지금도 지원은 되지만, 활발하게 새 기능이 추가되는 최신 UI 프레임워크는 아니며, Microsoft의 MFC 문서에도 MFC는 계속 지원되는 한편 새 기능 추가나 문서 갱신은 이루어지지 않는다는 주석이 보입니다.

이 때문에 MFC의 위치는 대체로 이렇게 됩니다.

기존 MFC 앱의 유지보수              -> 현실적으로 흔하다
기존 MFC 앱의 기능 추가              -> 있을 수 있다
MFC 앱의 빌드 환경 갱신              -> 중요
MFC에서 다른 UI로의 단계적 이행      -> 있을 수 있다
완전 신규의 일반적인 GUI 앱에서 채택 -> 신중히 판단

특히 오래 쓰여 온 업무 애플리케이션에서는 UI, 인쇄, 파일 입출력, 디바이스 제어, 독자 프로토콜, COM 연동이 MFC 안에 모여 있는 경우가 있습니다.

그런 코드베이스에서는 「MFC를 버린다」보다 먼저 「MFC를 읽을 수 있는 상태로 만든다」가 필요합니다.

4. MFC가 잘했던 영역

MFC가 쓰여 온 대표적인 영역은 Windows 네이티브 데스크톱 애플리케이션입니다.

구체적으로는 이런 애플리케이션입니다.

대화 상자 중심의 업무 도구
파일을 열어 편집하는 SDI 앱
여러 문서를 다루는 MDI 앱
계측 기기나 제조 장치의 제어 화면
CAD/CAM 계열의 네이티브 앱
인쇄와 미리보기를 많이 쓰는 앱
ActiveX나 OLE 연동을 포함하는 앱
오래된 Windows API나 COM 자산과 밀접한 앱

MFC의 강점은 Windows 네이티브 부품과 가까운 곳에서 동작한다는 점입니다. 창, 메뉴, 도구 모음, 상태 표시줄, 대화 상자, 공용 컨트롤, 인쇄, 파일 대화 상자, 레지스트리, GDI 그리기 등을 C++ 클래스로 다룰 수 있습니다.

한편 MFC의 약점은 현대적인 UI 구축, 데이터 바인딩, 테스트 용이성, 비동기 처리, 모던한 레이아웃, 고 DPI 대응, 다국어화, 접근성 등이 최근 프레임워크만큼 자연스럽게는 작성되지 않는다는 점입니다.

특징을 나열하면 이렇게 됩니다.

Windows 네이티브에 가깝다
C++로 직접 제어할 수 있다
기존 자산이 많다
Win32 지식이 필요하다
오래된 관례가 많다
테스트하기 쉬운 구조는 스스로 다듬어야 한다

5. Visual Studio에서 MFC를 쓰기 위한 준비

Visual Studio에 C++를 설치했더라도 MFC가 반드시 들어 있는 것은 아닙니다. MFC는 Visual Studio Installer의 개별 컴포넌트로 다루어지기 때문입니다. 대표적으로는 이 부근의 컴포넌트를 확인합니다.

C++를 사용한 데스크톱 개발
MSVC v143 - VS 2022 C++ x64/x86 build tools
Windows SDK
C++ MFC for latest v143 build tools
C++ ATL for latest v143 build tools
Spectre Mitigations판 MFC가 필요한지 여부

빌드 시 MFC 관련 파일을 찾지 못하면, 프로젝트 설정뿐 아니라 Visual Studio 쪽에 MFC 컴포넌트가 들어 있는지를 확인합니다.

설치 관리자에서의 선택 방법은 다음 절차입니다.

1. 시작 메뉴에서 「Visual Studio Installer」를 실행한다
2. 대상의 설치된 Visual Studio에서 「수정」을 누른다
3. 「워크로드」 탭에서 「C++를 사용한 데스크톱 개발」에 체크한다
4. 같은 화면 오른쪽 「설치 세부 정보」에서
   「C++ MFC for latest v143 build tools」에 체크한다
5. 찾을 수 없으면 「개별 구성 요소」 탭으로 전환하고,
   검색 상자에 MFC를 입력해 찾는다
6. 「수정」을 눌러 설치한다

「C++를 사용한 데스크톱 개발」 워크로드만 골랐다고 MFC가 들어가지 않는 경우가 있으므로, 절차 4 또는 절차 5에서 컴포넌트 자체에 체크가 되어 있는지를 반드시 확인합니다.

스크립트나 CI에서 넣는 경우에는 컴포넌트 ID를 지정합니다.

표시 이름 컴포넌트 ID
C++ MFC for latest v143 build tools Microsoft.VisualStudio.Component.VC.ATLMFC
C++ ATL for latest v143 build tools Microsoft.VisualStudio.Component.VC.ATL
C++를 사용한 데스크톱 개발(Build Tools 워크로드) Microsoft.VisualStudio.Workload.VCTools

들어 있는지는 MFC 헤더가 놓이는 atlmfc 폴더의 유무로 확인할 수 있습니다. MFC의 헤더와 라이브러리는 MSVC 도구 집합 아래에 들어갑니다.

<Visual Studio 설치 경로>\VC\Tools\MSVC\<도구 집합 버전>\atlmfc\include\afxwin.h

PowerShell로 찾으려면 다음과 같이 합니다.

Get-ChildItem -Path "$env:ProgramFiles\Microsoft Visual Studio\2022" -Recurse -Filter afxwin.h -ErrorAction SilentlyContinue |
    Select-Object -ExpandProperty FullName

아무것도 표시되지 않으면 MFC 컴포넌트가 들어 있지 않습니다.

이 상태에서 빌드하면 afxwin.h를 가져오는 단계에서 멈춥니다. 전형적으로는 다음 컴파일 오류가 됩니다.

fatal error C1083: Cannot open include file: 'afxwin.h': No such file or directory

이 오류를 「include 경로 설정 실수」로 읽어 프로젝트 속성을 찾아다니는 것이 흔한 헛수고입니다. afxwin.hafxdialogex.h를 찾지 못할 때는 먼저 Visual Studio Installer 쪽을 의심합니다.

CI 환경이나 빌드 서버에서도 같습니다. 로컬 Visual Studio에서는 빌드되는데 CI에서는 실패하는 경우, MFC 컴포넌트 유무나 대상 도구 집합 버전 차이가 원인이 되는 경우가 있습니다.

6. MFC 애플리케이션의 기본 구조

MFC 애플리케이션은 대체로 다음과 같은 구조를 갖습니다.

CWinApp 파생 클래스
  애플리케이션 전체의 초기화와 종료를 담당한다

CFrameWnd / CMDIFrameWnd / CDialog 파생 클래스
  메인 창이나 대화 상자를 담당한다

CView 파생 클래스
  화면 표시와 사용자 조작을 담당한다

CDocument 파생 클래스
  데이터와 파일 저장을 담당한다

리소스 파일
  메뉴, 대화 상자, 아이콘, 문자열 등을 보관한다

메시지 맵
  Windows 메시지와 명령을 핸들러 함수에 연결한다

예를 들어 단순한 MFC 앱에서는 이런 CWinApp 파생 클래스가 나옵니다.

class CMyApp : public CWinApp
{
public:
    virtual BOOL InitInstance();
};

CMyApp theApp;

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

    CMainFrame* pFrame = new CMainFrame;
    m_pMainWnd = pFrame;

    pFrame->Create(nullptr, _T("My MFC Application"));
    pFrame->ShowWindow(SW_SHOW);
    pFrame->UpdateWindow();

    return TRUE;
}

CWinApp는 애플리케이션 전체를 나타내는 클래스입니다. MFC 애플리케이션에서는 보통 CWinApp에서 파생한 객체가 1개 존재합니다.

theApp과 같은 전역 객체는 처음에는 어색할 수 있지만, MFC에서는 이것이 표준적인 구조입니다.

7. CWinApp는 무엇을 하는가

CWinApp는 MFC 애플리케이션의 입구로서 중요합니다. 일반적인 Win32 애플리케이션에서는 WinMain, 창 클래스 등록, 메시지 루프 등을 직접 쓰지만, MFC에서는 그 대부분을 프레임워크가 맡습니다. 개발자는 주로 InitInstance를 오버라이드해 애플리케이션 고유의 초기화를 작성합니다.

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

    // 설정 읽기
    // COM 초기화
    // 메인 창 생성
    // 문서 템플릿 등록

    return TRUE;
}

InitInstance에 쓰기 쉬운 것은 이런 처리입니다.

공용 컨트롤 초기화
레지스트리 키 설정
최근 사용한 파일 목록 읽기
문서 템플릿 등록
메인 프레임 생성
명령줄 인자 처리
COM/OLE 초기화

유지보수 시에는 먼저 CWinApp 파생 클래스를 확인하면 애플리케이션 전체의 시작 순서가 보이기 쉬워집니다.

8. CWnd는 MFC의 중심에 있는 클래스

MFC UI 클래스의 대부분은 Windows 창을 나타내는 CWnd를 기반으로 합니다. 다만 CWnd 객체와 HWND는 같은 것이 아닙니다.

HWND
  Windows OS가 관리하는 창 핸들

CWnd
  HWND를 다루기 쉽게 하기 위한 C++ 래퍼 객체

MFC에서는 CWnd가 내부에 HWND를 갖습니다.

HWND hWnd = m_hWnd;

또는 다음과 같이 가져옵니다.

HWND hWnd = GetSafeHwnd();

유지보수 시 중요한 것은, CWnd*가 존재하더라도 대응하는 HWND가 이미 폐기되어 있을 수 있다는 점입니다.

그래서 창이 유효한지는 이런 형태로 확인합니다.

if (pWnd != nullptr && ::IsWindow(pWnd->GetSafeHwnd()))
{
    pWnd->ShowWindow(SW_SHOW);
}

MFC의 버그 조사에서는 CWnd의 C++ 객체 수명과 실제 Windows 창 핸들의 수명이 어긋나지 않았는지를 보는 것이 중요합니다.

9. 메시지 맵이란 무엇인가

MFC다움이 가장 잘 드러나는 메커니즘 중 하나가 메시지 맵입니다.

Windows 애플리케이션은 마우스 클릭, 키 입력, 다시 그리기, 창 크기 변경, 메뉴 선택 등을 Windows 메시지로 받습니다.

Win32 API에서는 보통 WndProcswitch문으로 메시지를 처리합니다.

MFC에서는 그것을 메시지 맵으로 핸들러 함수에 연결합니다.

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_BN_CLICKED(IDC_BUTTON_OK, &CMyDialog::OnClickedButtonOk)
    ON_WM_CLOSE()
END_MESSAGE_MAP()

void CMyDialog::OnClickedButtonOk()
{
    AfxMessageBox(_T("Clicked"));
}

이 코드의 의미는 이렇습니다.

IDC_BUTTON_OK라는 버튼이 클릭되면
CMyDialog::OnClickedButtonOk를 호출한다

메시지가 도착한 뒤 핸들러가 호출되기까지의 흐름을 그림으로 그리면 이렇게 됩니다.

있다없다찾았다끝까지 없다Windows 메시지WM_COMMAND / WM_PAINT 등메시지 루프MFC 프레임워크가 돌린다CWnd::WindowProcMFC가 준비한 공통 창구자기 클래스의 메시지 맵에일치하는 항목이 있는가ON_BN_CLICKED 등이 가리키는핸들러 함수를 호출한다기본 클래스의 메시지 맵을 찾는다CDialogEx에서 CDialog, CWnd로 차례로 따라간다어딘가에서 찾았는가DefWindowProcWindows 기본 처리에 맡긴다

switch문이 보이지 않는 것은, 이 「자기 클래스에서 기본 클래스로 메시지 맵을 따라가는」 부분을 프레임워크가 대신하기 때문입니다. DECLARE_MESSAGE_MAPBEGIN_MESSAGE_MAP은 이 탐색을 위해 클래스마다 대응표를 준비하는 매크로라고 생각하면 이해하기 쉬워집니다.

MFC에 익숙하지 않으면 함수가 어디서 호출되는지 알기 어렵게 느껴집니다. 검색해도 직접 호출을 찾지 못하면 메시지 맵을 봅니다.

함수가 직접 호출되지 않는다
그런데 이벤트 시점에 실행된다
-> BEGIN_MESSAGE_MAP / ON_... 매크로를 확인한다

MFC 코드 리뷰에서는 핸들러 함수만 보지 말고 메시지 맵과 세트로 확인하는 것이 중요합니다.

10. 명령 라우팅

MFC에서는 메뉴나 도구 모음 조작도 명령으로 다루어집니다.

대표적인 것은 ON_COMMAND입니다.

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)
END_MESSAGE_MAP()

void CMainFrame::OnFileOpen()
{
    // 파일을 여는 처리
}

MFC에는 명령을 적절한 객체로 전달하는 메커니즘이 있습니다.

예를 들어 같은 ID_EDIT_COPY라도, 현재 활성 뷰, 문서, 프레임, 애플리케이션 중 어느 쪽이 처리하는지가 바뀌는 경우가 있습니다.

활성 뷰
문서
프레임 창
애플리케이션

이런 순서로 처리할 수 있는 객체에 명령이 넘어갑니다.

그래서 MFC에서는 「메뉴를 누르면 어느 함수가 호출되는가」를 단순한 문자열 검색만으로는 따라가기 어려운 경우가 있습니다.

유지보수 시 볼 점은 이 부근입니다.

명령 ID는 무엇인가
ON_COMMAND는 어느 클래스에 있는가
ON_UPDATE_COMMAND_UI는 어디에 있는가
현재 활성 뷰는 어느 것인가
Document/View 구조를 사용하는가

11. ON_UPDATE_COMMAND_UI란 무엇인가

MFC에서는 메뉴 항목이나 도구 모음 버튼의 사용/사용 안 함, 체크 상태, 표시 텍스트 등을 갱신하기 위해 ON_UPDATE_COMMAND_UI를 쓰는 경우가 있습니다.

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_COMMAND(ID_EDIT_DELETE, &CMainFrame::OnEditDelete)
    ON_UPDATE_COMMAND_UI(ID_EDIT_DELETE, &CMainFrame::OnUpdateEditDelete)
END_MESSAGE_MAP()

void CMainFrame::OnUpdateEditDelete(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(CanDeleteCurrentItem());
}

이로써 삭제할 수 있는 상태일 때만 메뉴나 버튼을 사용할 수 있게 합니다.

MFC 앱을 쓰다가 버튼이 왠지 회색이 되거나, 메뉴를 누를 수 없거나, 체크 상태가 바뀌는 동작을 조사할 때는 ON_UPDATE_COMMAND_UI를 찾으면 원인이 나오는 경우가 있습니다.

12. 대화 상자 기반 MFC 앱

MFC에서 가장 이해하기 쉬운 형태 중 하나가 대화 상자 기반 애플리케이션입니다.

설정 화면, 간단한 업무 도구, 장치 조작 화면 등에서는 대화 상자를 중심으로 구성되어 있는 경우가 있습니다.

전형적으로는 CDialog 또는 CDialogEx를 상속합니다.

class CSettingsDialog : public CDialogEx
{
public:
    CSettingsDialog(CWnd* pParent = nullptr);

#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_SETTINGS_DIALOG };
#endif

protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();

    afx_msg void OnBnClickedOk();
    DECLARE_MESSAGE_MAP()

private:
    CString m_name;
    int m_interval;
};

대화 상자 기반 코드에서는 다음 요소가 자주 나옵니다.

IDD_...        대화 상자 리소스 ID
IDC_...        컨트롤 ID
OnInitDialog   초기화 처리
DoDataExchange 컨트롤과 멤버 변수의 연결
UpdateData     화면과 변수의 동기화
ON_BN_CLICKED  버튼 클릭 처리

대화 상자는 겉모습만이 아니라 리소스, 멤버 변수, 메시지 맵, 초기화 처리가 맞물려 동작합니다.

13. DDX와 DDV

MFC 대화 상자에서 자주 나오는 것이 DDXDDV입니다.

DDX = Dialog Data Exchange
DDV = Dialog Data Validation

DDX는 대화 상자 위의 컨트롤과 C++ 멤버 변수를 대응시키는 메커니즘이고, DDV는 그 입력값을 검증하는 메커니즘입니다.

void CSettingsDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_NAME, m_name);
    DDX_Text(pDX, IDC_EDIT_INTERVAL, m_interval);
    DDV_MinMaxInt(pDX, m_interval, 1, 3600);
}

UpdateData(TRUE)를 호출하면 화면의 입력값이 멤버 변수에 반영됩니다.

void CSettingsDialog::OnBnClickedOk()
{
    if (!UpdateData(TRUE))
    {
        return;
    }

    // 여기에서는 m_name이나 m_interval에 화면 입력값이 들어 있다
    SaveSettings(m_name, m_interval);

    CDialogEx::OnOK();
}

반대로 UpdateData(FALSE)를 호출하면 멤버 변수의 값이 화면에 반영됩니다.

BOOL CSettingsDialog::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    m_name = _T("default");
    m_interval = 60;
    UpdateData(FALSE);

    return TRUE;
}

MFC 대화 상자에서 입력값이 이상할 때는 이 부근을 확인합니다.

DoDataExchange에 DDX가 정의되어 있는가
UpdateData(TRUE)를 호출하고 있는가
UpdateData(FALSE)를 호출하는 시점은 올바른가
DDV에서 입력이 걸러지지 않았는가
컨트롤 ID가 리소스와 일치하는가

14. Document/View 아키텍처

MFC의 큰 특징 중 하나가 Document/View 아키텍처입니다.

이는 애플리케이션이 다루는 데이터와 그 표시를 분리하기 위한 구조입니다.

CDocument
  데이터를 보관한다
  파일 읽기/쓰기를 담당한다
  여러 뷰에 갱신 알림을 보낸다

CView
  데이터를 표시한다
  사용자 조작을 다룬다
  그리기와 선택 상태를 관리한다

프레임, 뷰, 문서의 관계를 그림으로 그리면 이렇게 됩니다.

GetDocument로 가져온다UpdateAllViews로 알린다CWinApp 파생앱 전체의 시작과 종료문서 템플릿CSingleDocTemplate / CMultiDocTemplate프레임 창CFrameWnd / CMDIChildWndCDocument 파생데이터와 파일 입출력CView 파생그리기와 사용자 조작

핵심은 프레임, 문서, 뷰 셋을 연결하는 것이 문서 템플릿이라는 점입니다. InitInstance 안에서 이 템플릿을 등록하므로 「어느 뷰가 어느 문서를 보는가」를 알고 싶을 때는 먼저 InitInstance를 읽습니다.

뷰에서 문서를 볼 때는 GetDocument, 문서 쪽 변경을 모든 뷰에 전할 때는 UpdateAllViews를 쓴다는 방향의 차이도 잡아 두면, 화면이 갱신되지 않는 결함을 따라가기 쉬워집니다.

예를 들어 텍스트 에디터, 도형 에디터, 설정 파일 편집 도구, CAD 같은 앱에서는 데이터와 표시를 나누는 의미가 있습니다.

class CMyDocument : public CDocument
{
public:
    std::vector<Item> m_items;

    virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
    virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
};

class CMyView : public CView
{
protected:
    virtual void OnDraw(CDC* pDC);

    CMyDocument* GetDocument() const;
};

뷰 쪽에서는 문서를 가져와 그립니다.

void CMyView::OnDraw(CDC* pDC)
{
    CMyDocument* pDoc = GetDocument();
    if (pDoc == nullptr)
    {
        return;
    }

    for (const auto& item : pDoc->m_items)
    {
        // pDC를 사용해 그린다
    }
}

Document/View의 이점은 같은 데이터를 여러 뷰로 표시하기 쉽다는 점입니다.

예를 들어 같은 데이터를 이렇게 보여줄 수 있습니다.

표 형식 뷰
그래프 뷰
상세 뷰
미리보기
인쇄 뷰

다만 단순한 설정 화면이나 작은 도구에 Document/View를 쓰면 오히려 구조가 무겁게 느껴지는 경우도 있습니다.

유지보수 시에는 그 앱이 Document/View를 쓰는지, 대화 상자 중심인지를 먼저 가리면 코드를 따라가기 쉬워집니다.

15. SDI와 MDI

MFC에서는 Document/View와 조합해 SDI나 MDI라는 구조가 자주 나옵니다.

SDI = Single Document Interface
MDI = Multiple Document Interface

SDI는 기본적으로 1개의 프레임에서 1개의 문서를 다루는 형태입니다.

메인 창
  1개의 문서
  1개 또는 여러 뷰

MDI는 1개의 부모 창 안에 여러 자식 창을 두고, 각각이 문서를 다루는 형태입니다.

MDI 부모 프레임
  MDI 자식 프레임1 -> 문서1
  MDI 자식 프레임2 -> 문서2
  MDI 자식 프레임3 -> 문서3

오래된 Windows 애플리케이션에서는 MDI가 자주 쓰였습니다.

유지보수 시에는 클래스 이름을 보면 구조의 감이 잡힙니다.

CFrameWnd       SDI 계열 프레임
CMDIFrameWnd    MDI 부모 프레임
CMDIChildWnd    MDI 자식 프레임
CSingleDocTemplate SDI용 문서 템플릿
CMultiDocTemplate  MDI용 문서 템플릿

MFC 마법사로 만든 앱에서는 InitInstance 안에 CSingleDocTemplateCMultiDocTemplate 등록 처리가 있는 경우가 많습니다.

16. 리소스 파일을 이해한다

MFC 앱에서는 .rc 파일이 매우 중요합니다.

.rc는 Windows의 리소스 파일입니다.

여기에는 이런 것들이 정의되어 있습니다.

대화 상자 템플릿
메뉴
액셀러레이터 키
아이콘
비트맵
문자열 테이블
버전 정보
도구 모음

또한 resource.h에는 리소스 ID가 정의됩니다.

#define IDD_SETTINGS_DIALOG  101
#define IDC_EDIT_NAME        1001
#define IDC_EDIT_INTERVAL    1002
#define ID_FILE_OPEN         32771

MFC 코드에서는 이 ID를 써서 리소스와 C++ 코드를 연결합니다.

DDX_Text(pDX, IDC_EDIT_NAME, m_name);
ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)

유지보수 시 흔한 문제는 리소스 ID 불일치입니다.

resource.h의 ID가 바뀌었다
다른 브랜치 병합으로 ID가 충돌했다
대화 상자 위 컨트롤 ID와 DDX의 ID가 맞지 않는다
삭제했을 메뉴 ID가 남아 있다
문자열 테이블의 ID가 중복된다

MFC 앱의 동작을 조사할 때는 C++ 코드뿐 아니라 .rcresource.h도 동시에 봐야 합니다.

17. Class Wizard와 손수 작성한 코드

MFC에는 Visual Studio의 Class Wizard와 깊게 묶인 역사가 있습니다.

Class Wizard를 쓰면 메시지 핸들러, DDX 변수, 가상 함수 오버라이드 등을 자동 생성할 수 있습니다.

그래서 MFC 코드에는 도구가 생성한 형태의 코드가 많이 남아 있습니다.

//{{AFX_DATA(CSettingsDialog)
//}}AFX_DATA

//{{AFX_MSG(CSettingsDialog)
//}}AFX_MSG

새 Visual Studio에서는 보이는 모습이나 생성되는 형태가 바뀌어 있는 경우도 있지만, 오래된 코드베이스에서는 이런 주석 마커가 남아 있는 경우가 있습니다.

유지보수 시 중요한 것은 생성 코드와 손수 작성한 코드의 경계를 함부로 깨지 않는 것입니다.

메시지 맵을 삭제하지 않는다
DDX 대응을 깨지 않는다
리소스 ID를 함부로 바꾸지 않는다
오래된 Class Wizard 전제의 주석을 무작정 지우지 않는다

MFC에서는 단순히 C++로서 컴파일이 되는 것만으로는 부족합니다. Visual Studio의 리소스 에디터나 Class Wizard가 기대하는 형태도 어느 정도는 유지해야 합니다.

18. CString과 문자열

MFC에서 자주 등장하는 문자열 클래스가 CString입니다.

CString name = _T("Komura");
CString message;
message.Format(_T("Hello, %s"), name.GetString());

CString은 MFC/ATL 계열 코드에서 자주 쓰이는 가변 길이 문자열 클래스입니다. 현대 C++에서는 std::string이나 std::wstring을 쓰는 경우가 많지만, MFC에서는 API나 컨트롤과의 궁합 때문에 CString이 많이 쓰입니다.

유지보수 시에는 문자 코드를 의식해야 합니다.

CString      프로젝트 설정에 따라 CStringA 또는 CStringW에 해당
CStringA     ANSI / MBCS 계열
CStringW     Unicode / UTF-16 계열
LPCTSTR      TCHAR 기반 문자열 포인터
LPCSTR       char 계열
LPCWSTR      wchar_t 계열
std::string  보통은 char 계열
std::wstring wchar_t 계열

지금의 Windows 앱에서는 기본적으로 Unicode를 전제로 하는 편이 안전하지만, 오래된 MFC 앱에서는 MBCS 전제 코드가 남아 있는 경우가 있습니다.

CString text = _T("日本語");
std::wstring ws(text.GetString());

문자열 변환 버그는 MFC 앱 유지보수에서 자주 나옵니다.

특히 주의하고 싶은 것은 이런 경우입니다.

Shift_JIS 전제 파일을 읽는다
Unicode 빌드로 전환한다
외부 DLL이 char*를 요구한다
COM이 BSTR을 요구한다
std::string으로 쉽게 변환해 문자가 깨진다

CString을 보면 단순히 「오래된 문자열 클래스다」라고 생각하지 말고, 프로젝트의 문자 집합 설정, 외부 API, 파일 형식과 세트로 확인하는 것이 중요합니다.

19. CFile과 CArchive

MFC에는 파일 조작이나 직렬화를 위한 클래스도 있습니다. 대표적인 것이 CFileCArchive입니다.

CFile file;
if (file.Open(path, CFile::modeRead))
{
    CArchive ar(&file, CArchive::load);
    // ar에서 읽는다
}

CArchive는 MFC의 직렬화 구조에서 자주 쓰입니다.

CDocument 파생 클래스에서는 Serialize를 오버라이드해 읽기와 저장을 같은 함수에 쓰는 경우가 있습니다.

void CMyDocument::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        ar << m_title;
        ar << static_cast<int>(m_items.size());
        for (const auto& item : m_items)
        {
            ar << item.Name;
            ar << item.Value;
        }
    }
    else
    {
        int count = 0;
        ar >> m_title;
        ar >> count;
        m_items.clear();
        for (int i = 0; i < count; ++i)
        {
            Item item;
            ar >> item.Name;
            ar >> item.Value;
            m_items.push_back(item);
        }
    }
}

MFC의 직렬화는 편리하지만 장기 운용에서는 주의가 필요합니다.

오래된 파일 형식과의 호환성
버전 번호 관리
읽기 실패 시 복구
예외 처리
문자 코드
엔디언
구조체를 그대로 저장하고 있지 않은가

독자 바이너리 형식을 오래 쓰는 MFC 앱에서는 Serialize가 사실상의 파일 사양이 되어 있는 경우도 있습니다.

이 경우 코드를 바꾸기 전에 기존 파일을 읽는 테스트 데이터를 반드시 준비하는 편이 좋습니다.

20. GDI 그리기와 CDC

MFC의 화면 그리기에서는 Windows Device Context를 다루기 위한 CDC 클래스가 자주 쓰입니다. CView::OnDraw에서는 인자로 CDC*가 전달됩니다.

void CMyView::OnDraw(CDC* pDC)
{
    pDC->TextOut(10, 10, _T("Hello MFC"));
    pDC->Rectangle(10, 40, 200, 120);
}

펜이나 브러시를 쓸 때는 선택과 복원에 주의합니다.

void CMyView::OnDraw(CDC* pDC)
{
    CPen pen(PS_SOLID, 1, RGB(0, 0, 0));
    CPen* pOldPen = pDC->SelectObject(&pen);

    pDC->MoveTo(10, 10);
    pDC->LineTo(100, 100);

    pDC->SelectObject(pOldPen);
}

GDI 객체 주변에서는 이런 실수가 문제가 됩니다.

SelectObject한 뒤에 원래대로 되돌리지 않았다
GDI 객체를 대량으로 만들고 폐기하지 않았다
OnPaint와 OnDraw의 역할을 혼동한다
더블 버퍼링하지 않아 깜빡인다
고 DPI에서 고정 픽셀 전제 그리기가 깨진다

MFC의 그리기 버그에서는 C++ 로직뿐 아니라 Windows의 GDI 리소스, 다시 그리기 타이밍, DPI, 글꼴 크기를 확인해야 합니다.

21. 모달 대화 상자와 모달리스 대화 상자

MFC에서는 대화 상자를 띄우는 방식에도 주의가 필요합니다.

모달 대화 상자는 DoModal로 표시합니다.

CSettingsDialog dlg(this);
if (dlg.DoModal() == IDOK)
{
    // OK 시의 처리
}

이 경우 대화 상자가 닫힐 때까지 호출한 쪽은 기다립니다.

한편 모달리스 대화 상자는 만든 뒤에도 호출한 쪽의 처리가 돌아옵니다.

m_pToolDialog = new CToolDialog(this);
m_pToolDialog->Create(IDD_TOOL_DIALOG, this);
m_pToolDialog->ShowWindow(SW_SHOW);

모달리스 대화 상자에서는 수명 관리가 중요합니다.

new한 대화 상자를 언제 delete하는가
부모 창이 먼저 폐기되지 않는가
대화 상자 쪽에서 PostNcDestroy를 쓰는가
이중 생성이 되지 않는가
닫은 뒤의 포인터가 남아 있지 않은가

MFC의 크래시 조사에서는 모달리스 대화 상자의 수명 문제가 원인이 되는 경우가 있습니다.

22. C++ 객체와 Windows 핸들의 수명

MFC에서 매우 중요한 것이 C++ 객체와 Windows 핸들의 수명 차이입니다. 예를 들어 CWnd는 C++ 객체이지만 실제 창은 Windows가 HWND로 관리하며, 이 둘은 항상 동시에 만들어져 동시에 사라지는 것은 아닙니다.

CWnd 객체는 있지만 HWND가 아직 없다
HWND는 폐기됐지만 CWnd 객체는 남아 있다
일시적인 CWnd 래퍼가 만들어져 있다
Attach/Detach로 핸들을 바꿔 달고 있다

예를 들어 이런 코드는 주의가 필요합니다.

CWnd* pWnd = GetDlgItem(IDC_SOME_CONTROL);
// pWnd를 멤버에 저장해 나중에 쓴다

GetDlgItem으로 얻은 포인터를 장기간 보관하면 창이 폐기된 뒤에 참조할 위험이 있습니다.

필요하면 매번 GetDlgItem하거나, 컨트롤용 멤버 변수를 DDX로 관리하는 편이 안전한 경우가 있습니다.

DDX_Control(pDX, IDC_LIST_ITEMS, m_listItems);

MFC에서는 포인터가 non-null이어도 안전하다고 할 수 없습니다.

if (m_pDialog != nullptr && ::IsWindow(m_pDialog->GetSafeHwnd()))
{
    m_pDialog->SetWindowText(_T("Running"));
}

이 감각은 MFC 유지보수에서 매우 중요합니다.

23. 스레드와 UI 갱신

Windows의 UI는 기본적으로 만들어진 UI 스레드에서 조작해야 하며, MFC 앱에서도 같습니다. 워커 스레드에서 UI 컨트롤을 직접 조작하면 불안정한 동작이나 크래시의 원인이 됩니다.

피하고 싶은 예입니다.

UINT WorkerThreadProc(LPVOID pParam)
{
    CMyDialog* pDlg = static_cast<CMyDialog*>(pParam);

    // 워커 스레드에서 UI를 직접 건드리는 것은 피한다
    pDlg->SetDlgItemText(IDC_STATUS, _T("Done"));

    return 0;
}

일반적으로는 PostMessage 등으로 UI 스레드에 알립니다.

constexpr UINT WM_APP_WORK_DONE = WM_APP + 1;

UINT WorkerThreadProc(LPVOID pParam)
{
    HWND hWnd = static_cast<HWND>(pParam);

    // 무거운 처리

    ::PostMessage(hWnd, WM_APP_WORK_DONE, 0, 0);
    return 0;
}

UI 쪽에서는 메시지 맵으로 받습니다.

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_MESSAGE(WM_APP_WORK_DONE, &CMyDialog::OnWorkDone)
END_MESSAGE_MAP()

LRESULT CMyDialog::OnWorkDone(WPARAM, LPARAM)
{
    SetDlgItemText(IDC_STATUS, _T("Done"));
    return 0;
}

MFC의 스레드 주변에서는 다음을 확인합니다.

UI를 워커 스레드에서 직접 건드리지 않았는가
창 폐기 후에 PostMessage하지 않았는가
스레드 종료 대기로 UI 스레드를 멈추지 않았는가
공유 데이터 잠금은 적절한가
AfxBeginThread의 반환값과 수명을 오해하지 않았는가

24. MFC DLL과 모듈 상태

MFC에서 DLL을 만들 경우 모듈 상태라는 개념이 나옵니다.

MFC DLL에서 리소스를 읽고, 대화 상자를 표시하고, 확장 DLL을 만드는 장면에서는 어느 모듈의 리소스를 보러 가는지가 문제가 됩니다.

MFC DLL의 함수 입구에서 이런 매크로를 보는 경우가 있습니다.

AFX_MANAGE_STATE(AfxGetStaticModuleState());

이것은 MFC가 올바른 모듈 상태를 쓸 수 있게 하기 위한 것입니다.

이 매크로를 잊으면 이런 결함으로 이어집니다.

DLL 안의 대화 상자 리소스를 찾지 못한다
문자열 리소스가 다른 모듈에서 읽힌다
아이콘이나 메뉴를 찾지 못한다
디버그 때만 되고 릴리스에서 깨진다

MFC DLL을 유지보수할 때는 EXE, 일반 DLL, 확장 DLL, 리소스 DLL의 관계를 정리하는 것이 중요합니다.

특히 비 MFC 앱에서 MFC DLL을 호출하는 경우나 플러그인 구성인 경우에는 주의가 필요합니다.

25. MFC를 정적 링크할지 공유 DLL로 쓸지

MFC 앱에서는 프로젝트 설정에 「Use of MFC」가 있습니다.

대표적인 선택지는 이 2가지입니다.

Use MFC in a Shared DLL
Use MFC in a Static Library

공유 DLL을 쓰는 경우 실행 환경에 대응하는 MFC 런타임이나 Visual C++ 런타임이 필요합니다.

정적 링크의 경우 배포물은 단순해 보일 수 있지만, 실행 파일 크기나 갱신, 보안 수정 반영, 라이선스나 재배포 조건 등을 생각할 필요가 있습니다.

어느 쪽이 항상 정답인 것은 아닙니다.

판단 재료는 이 부근입니다.

배포처에 Visual C++ Redistributable을 넣을 수 있는가
앱을 단일 exe에 가깝게 하고 싶은가
보안 업데이트를 어떻게 반영하는가
여러 앱에서 같은 런타임을 공유하는가
설치 프로그램을 준비할 수 있는가
대상 Windows 버전은 무엇인가

유지보수에서는 먼저 현재 설정을 확인하는 것이 중요합니다.

Configuration Properties
  General
    Use of MFC

더불어 Runtime Library 설정도 봅니다.

/MD   Multi-threaded DLL
/MDd  Multi-threaded Debug DLL
/MT   Multi-threaded
/MTd  Multi-threaded Debug

MFC와 CRT의 링크 설정이 섞이면 라이브러리 경계에서 메모리 확보/해제 문제가 나오는 경우가 있습니다.

26. Unicode, MBCS, TCHAR

오래된 MFC 코드에서는 TCHAR, LPCTSTR, _T() 매크로가 자주 나옵니다.

CString title = _T("설정");
SetWindowText(title);

이것은 Unicode 빌드와 MBCS 빌드 양쪽에 대응하기 위한 작성법입니다.

Unicode 빌드
  TCHAR    -> wchar_t
  LPCTSTR  -> const wchar_t*
  _T("...") -> L"..."

MBCS 빌드
  TCHAR    -> char
  LPCTSTR  -> const char*
  _T("...") -> "..."

지금은 Unicode 빌드가 일반적이지만, 오래된 앱에서는 MBCS 전제 처리가 남아 있는 경우가 있습니다.

특히 외부 파일, 통신 프로토콜, 오래된 DLL, 데이터베이스 연결, 시리얼 통신 등에서는 문자 코드의 전제를 확인할 필요가 있습니다.

주의하고 싶은 것은 Unicode화를 단순한 치환 작업으로 생각하지 않는 것입니다.

char 배열의 크기는 바이트 수인가 문자 수인가
strlen을 쓰지 않았는가
sizeof(buffer)를 문자 수로 쓰지 않았는가
외부 API는 UTF-16을 받는가 Shift_JIS를 받는가
파일 저장 형식은 바뀌어도 되는가

MFC의 문자열 주변을 고칠 때는 화면 표시뿐 아니라 파일 호환성과 외부 연동까지 확인합니다.

27. MFC와 COM/OLE/ActiveX

MFC는 COM, OLE, ActiveX와 관계가 깊은 애플리케이션에서도 쓰여 왔습니다.

오래된 업무 앱에서는 다음과 같은 요소가 남아 있는 경우가 있습니다.

OLE Automation
ActiveX Control
COM 서버
COM 클라이언트
IDispatch
BSTR
VARIANT
COleDispatchDriver
COleVariant

MFC 앱의 시작 처리에는 이런 코드가 나오는 경우가 있습니다.

if (!AfxOleInit())
{
    AfxMessageBox(_T("OLE initialization failed"));
    return FALSE;
}

COM/OLE를 쓰는 경우, MFC 문제로 보이지만 실제로는 COM 초기화, 스레드 모델, 참조 카운트, 등록 정보, 32bit/64bit 차이가 원인인 경우가 있습니다.

특히 주의하고 싶은 것은 32bit ActiveX나 COM 컴포넌트입니다.

32bit MFC 앱에서는 32bit COM을 쓴다
64bit MFC 앱에서는 64bit COM을 쓴다
32bit/64bit의 COM 등록은 별개다
오래된 ActiveX가 64bit에 대응하지 않은 경우가 있다

MFC 앱을 x64화할 때는 UI 코드뿐 아니라 COM/OLE 의존도 반드시 확인합니다.

28. 고 DPI 대응과 현대의 Windows

오래된 MFC 앱을 현대 Windows에서 돌리면 고 DPI 환경에서 표시가 깨지는 경우가 있습니다.

예를 들어 이런 문제입니다.

글자가 잘린다
버튼이 너무 작다
고정 픽셀 그리기가 어긋난다
여러 모니터에서 확대율이 다르면 깨진다
오래된 비트맵이 흐려진다
대화 상자 레이아웃이 빡빡해진다

Windows 데스크톱 앱에서는 애플리케이션이 DPI 대응 모드를 명시해야 합니다.

MFC 앱에서도 매니페스트, 리소스, 그리기 코드, 글꼴, 레이아웃을 확인할 필요가 있습니다.

특히 고정 좌표를 직접 쓴 이런 코드는 고 DPI에서 문제가 되기 쉽습니다.

pDC->TextOut(10, 10, _T("Status"));
pDC->Rectangle(10, 40, 200, 80);

고정 픽셀 전제 좌표는 DPI가 바뀌면 겉모습이 깨집니다.

유지보수 시 확인 관점은 이렇습니다.

애플리케이션 매니페스트의 DPI 설정
대화 상자 리소스의 글꼴
고정 픽셀 그리기
이미지 리소스의 해상도
여러 모니터에서의 동작
Windows 10 / Windows 11에서의 표시

MFC의 고 DPI 대응은 단순히 프로젝트 설정만 바꾸는 것으로 끝나지 않는 경우가 있습니다. 오래된 UI에서는 실제 화면 확인과 레이아웃 수정이 필요해집니다.

29. 예외 처리와 오류 처리

MFC에는 독자적인 예외 클래스와 오류 처리 관례가 있습니다.

오래된 코드에서는 이런 매크로를 봅니다.

TRY
{
    // 처리
}
CATCH(CFileException, e)
{
    e->ReportError();
}
END_CATCH

현대 C++의 try / catch와 섞여 있는 코드도 있습니다.

try
{
    DoSomething();
}
catch (const std::exception& ex)
{
    // 로그 출력
}

MFC 유지보수에서 주의하고 싶은 것은 다음입니다.

MFC 예외와 C++ 표준 예외가 섞여 있지 않은가
예외 객체의 수명을 오해하지 않았는가
오래된 THROW/CATCH 매크로를 이해하고 있는가
반환값 오류와 예외가 섞여 있지 않은가
AfxMessageBox만으로 로그가 남지 않는 상태가 되지 않았는가

업무 앱에서는 화면에 오류 메시지를 띄우는 것뿐 아니라 로그, 조작 이력, 입력값, 외부 연결 상태를 남기는 것이 중요합니다.

오래된 MFC 앱에서는 오류가 AfxMessageBox만으로 끝나는 경우가 있습니다.

AfxMessageBox(_T("저장에 실패했습니다"));

유지보수성을 높이려면 UI 표시와 로그 기록을 나누는 편이 좋습니다.

LogError(_T("Save failed"), path);
AfxMessageBox(_T("저장에 실패했습니다. 로그를 확인하세요."));

30. MFC와 현대 C++을 어떻게 공존시키는가

MFC 앱을 유지보수한다고 해서 전부를 오래된 C++ 작성법에 맞출 필요는 없습니다.

UI 층은 MFC 관례를 존중하면서, 도메인 로직이나 계산 처리는 현대 C++로 정리할 수 있습니다.

예를 들어 이렇게 나눕니다.

MFC층
  CDialog
  CView
  CDocument
  CString
  메시지 맵
  리소스 조작

비MFC층
  std::string / std::wstring
  std::vector
  std::optional
  std::variant
  std::filesystem
  단위 테스트 가능한 클래스
  비즈니스 로직

나쁜 형태는 모든 처리가 대화 상자 클래스에 채워져 있는 상태입니다.

void CMainDialog::OnBnClickedExecute()
{
    // 입력 가져오기
    // 파일 읽기
    // 통신
    // 계산
    // DB 갱신
    // 화면 갱신
    // 로그 출력
    // 예외 처리
}

이런 코드는 바꾸기 어렵고, 테스트하기 어렵고, 버그 조사도 어려워집니다.

개선한다면 MFC 클래스에서 로직을 밖으로 꺼냅니다.

void CMainDialog::OnBnClickedExecute()
{
    if (!UpdateData(TRUE))
    {
        return;
    }

    ExecuteRequest request;
    request.Name = ToStdWString(m_name);
    request.Interval = m_interval;

    ExecuteResult result = m_service.Execute(request);

    m_status = ToCString(result.Message);
    UpdateData(FALSE);
}

이렇게 하면 m_service.Execute는 MFC 없이 테스트할 수 있습니다.

MFC 기존 자산을 유지보수할 때 가장 효과적인 개선은 UI 클래스에서 로직을 조금씩 잘라 내는 것입니다.

31. 테스트하기 쉬운 MFC 코드로 만든다

MFC 앱은 그대로면 단위 테스트하기 어려운 경우가 많습니다.

이유는 UI, Win32, 파일, 통신, 데이터베이스, 전역 상태가 밀결합되기 쉽기 때문입니다.

테스트하기 쉽게 하기 위한 사고방식은 이렇습니다.

CDialog나 CView를 직접 테스트하려고 하지 않는다
먼저 비UI 로직을 잘라 낸다
MFC 형을 경계에서 변환한다
파일이나 통신을 인터페이스화한다
화면 이벤트 핸들러를 얇게 한다

예를 들어 순수한 C++ 클래스로 처리를 옮깁니다.

class PriceCalculator
{
public:
    int CalculateTotal(const std::vector<int>& prices) const
    {
        int total = 0;
        for (int price : prices)
        {
            total += price;
        }
        return total;
    }
};

MFC 쪽은 입력과 출력만 담당합니다.

void CPriceDialog::OnBnClickedCalculate()
{
    if (!UpdateData(TRUE))
    {
        return;
    }

    std::vector<int> prices = ParsePrices(ToStdWString(m_input));
    int total = m_calculator.CalculateTotal(prices);

    m_result.Format(_T("%d"), total);
    UpdateData(FALSE);
}

이 구조로 하면 PriceCalculatorParsePrices는 일반적인 C++ 테스트 프레임워크로 테스트할 수 있습니다.

MFC 앱 전체를 한 번에 다시 만들 필요는 없고, 우선 이벤트 핸들러 안에서 테스트 가능한 처리를 밖으로 꺼내는 것만으로도 효과가 있습니다.

32. 빌드 환경을 고정한다

MFC 앱의 유지보수에서는 빌드 환경 고정이 중요합니다.

오래된 코드베이스에서는 다음 차이로 빌드 결과가 바뀌는 경우가 있습니다.

Visual Studio의 버전
MSVC 도구 집합 버전
Windows SDK 버전
MFC/ATL 컴포넌트 유무
x86 / x64 / ARM64
Debug / Release
Unicode / MBCS
MFC의 정적 링크 / 공유 DLL
런타임 라이브러리 설정
미리 컴파일된 헤더

MFC 앱에서는 stdafx.hpch.h에 많은 의존이 모여 있는 경우도 있습니다.

#include "framework.h"
#include "MyApp.h"

어떤 파일만 컴파일 설정이 다르거나, 미리 컴파일된 헤더 설정이 어긋나서 빌드가 깨지는 경우가 있습니다.

유지보수 프로젝트에서는 이 정보를 README에 명시해 두면 이후가 편해집니다.

필요한 Visual Studio 버전
필요한 워크로드와 개별 컴포넌트
필요한 Windows SDK
대상 플랫폼
MFC 링크 방식
빌드 절차
CI 실행 방법
배포물 만드는 방법

「내 PC에서는 빌드된다」를 졸업하는 것이 MFC 유지보수의 첫걸음입니다.

33. CI에서 MFC를 빌드한다

MFC 앱도 CI에서 빌드할 수 있습니다.

다만 CI 환경에 MFC 컴포넌트가 들어 있어야 합니다.

Visual Studio Build Tools를 쓰는 경우 MFC/ATL 컴포넌트 ID를 지정해 설치해야 합니다. 지정하는 ID는 5장의 표와 같습니다.

확인하고 싶은 포인트를 적습니다.

Build Tools에 MFC가 들어 있는가
대상 도구 집합이 프로젝트와 일치하는가
Windows SDK가 들어 있는가
x86 빌드와 x64 빌드를 둘 다 확인하는가
리소스 컴파일러가 동작하는가
서명 처리가 있는가
설치 프로그램 생성도 CI 대상인가

MFC 앱에서는 UI 테스트까지 자동화하는 것은 쉽지 않지만, 적어도 이 부근의 자동화는 유효합니다.

Debug / Release 빌드
x86 / x64 빌드
정적 분석
단위 테스트
설치 프로그램 작성
산출물의 해시 저장
의존 DLL 확인

장기 유지보수에서는 빌드할 수 있는 상태를 유지하는 것만으로도 큰 가치가 있습니다.

34. MFC 앱 디버그에서 볼 곳

MFC 앱에서 결함을 따라갈 때는 이 순서로 보면 효율적입니다.

1. 어느 화면인가
2. 대화 상자인가, View인가, Frame인가
3. 조작에 대응하는 리소스 ID는 무엇인가
4. 메시지 맵에서 어느 함수로 가는가
5. UpdateData의 방향은 올바른가
6. Document/View라면 Document의 상태는 무엇인가
7. 명령 라우팅으로 다른 클래스로 흐르지 않았는가
8. 워커 스레드에서 UI를 건드리지 않았는가
9. 예외나 오류가 AfxMessageBox만으로 삼켜지지 않았는가
10. 릴리스 빌드 고유의 미초기화나 수명 문제는 없는가

예를 들어 「버튼을 눌러도 아무 일도 없다」는 결함이라면 여기를 확인합니다.

버튼의 IDC가 올바른가
ON_BN_CLICKED가 존재하는가
핸들러 시그니처는 올바른가
대화 상자 리소스가 다른 것이 아닌가
버튼이 사용 안 함으로 되어 있지 않은가
처리 도중에 UpdateData가 실패하지 않았는가
예외가 삼켜지지 않았는가

「메뉴를 누를 수 없다」면 이쪽입니다.

ON_UPDATE_COMMAND_UI에서 사용 안 함으로 되어 있지 않은가
명령 ID가 중복되지 않았는가
활성 뷰가 예상대로인가
Frame/View/Document/App 어디에 핸들러가 있는가

MFC는 겉의 이벤트와 실제 처리가 매크로와 라우팅으로 연결되어 있어, 익숙해지기 전까지는 호출 경로를 그림으로 그리면 이해하기 쉽습니다. 9장의 그림이 그 호출 경로의 기본형이 됩니다.

참고로 증상에서 감을 잡고 싶다면 35장의 표를 먼저 보세요. 흔한 함정과 각각을 어디에서 확인할지를 대응시켜 두었습니다.

35. 흔한 함정

MFC 유지보수에서 흔한 함정을 정리합니다.

메시지 맵을 보지 않고 함수 호출만 검색한다
CWnd*가 non-null이면 유효하다고 착각한다
HWND의 수명과 C++ 객체의 수명을 혼동한다
UpdateData(TRUE/FALSE)의 방향을 잘못 잡는다
ON_UPDATE_COMMAND_UI로 사용 안 함이 된 것을 눈치채지 못한다
resource.h의 ID 충돌을 눈치채지 못한다
워커 스레드에서 UI를 직접 건드린다
CString과 std::string 변환으로 문자가 깨진다
MBCS 전제 코드를 Unicode화로 깨뜨린다
MFC DLL에서 AFX_MANAGE_STATE를 잊는다
x86 전제 COM/ActiveX를 x64화로 깨뜨린다
GDI 객체의 선택/해제를 잘못한다
고 DPI에서 고정 좌표 레이아웃이 깨진다

각각에 대해 「어떤 증상으로 나오는가」와 「어디를 보고 확인하는가」를 대응시켜 둡니다. 디버그의 입구는 34장의 절차와 대응합니다.

함정 나오기 쉬운 증상 확인할 곳
메시지 맵을 보지 않고 함수 호출만 검색한다 호출자를 찾지 못하는데 실행된다 BEGIN_MESSAGE_MAP을 검색한다. 9장, 34장 절차 4
CWnd*가 non-null이면 유효하다고 착각한다 가끔 죽는다, 닫은 화면을 조작하면 죽는다 ::IsWindow(pWnd->GetSafeHwnd())를 거친다. 8장
HWND의 수명과 C++ 객체의 수명을 혼동한다 대화 상자를 닫은 뒤의 액세스 위반 GetDlgItem 반환값을 보관하지 않았는지. 22장
UpdateData의 방향을 잘못 잡는다 입력값이 반영되지 않는다, 초기값이 화면에 나오지 않는다 UpdateData(TRUE)UpdateData(FALSE)의 호출 위치. 13장, 34장 절차 5
ON_UPDATE_COMMAND_UI로 사용 안 함이 된 것을 눈치채지 못한다 버튼이나 메뉴가 회색이 되어 누를 수 없다 ON_UPDATE_COMMAND_UI의 핸들러. 11장, 34장의 「메뉴를 누를 수 없다」
resource.h의 ID 충돌을 눈치채지 못한다 다른 대화 상자나 메뉴 항목이 반응한다 resource.h.rc를 ID로 맞춰 본다. 16장
워커 스레드에서 UI를 직접 건드린다 재현하기 어려운 불안정한 크래시, 가끔 멈춘다 AfxBeginThread에 넘긴 함수 안에서 UI를 건드리지 않았는지. 23장
CStringstd::string 변환으로 문자가 깨진다 일본어만 깨진다, 끝이 잘린다 프로젝트의 문자 집합 설정과 변환 위치. 18장
MBCS 전제 코드를 Unicode화로 깨뜨린다 버퍼 길이 어긋남, 기존 파일을 읽지 못하게 된다 sizeof를 문자 수로 쓰지 않았는지. 26장
MFC DLL에서 AFX_MANAGE_STATE를 잊는다 DLL 안의 대화 상자나 문자열 리소스를 찾지 못한다 DLL의 공개 함수 선두. 24장
x86 전제 COM/ActiveX를 x64화로 깨뜨린다 64bit 빌드만 시작 시나 화면 표시에서 실패한다 COM 등록이 32bit 쪽에만 있지 않은지. 27장
GDI 객체의 선택과 해제를 잘못한다 오래 돌리면 그리기가 깨진다 작업 관리자의 「GDI 객체」 열이 계속 늘지 않는지. 20장
고 DPI에서 고정 좌표 레이아웃이 깨진다 확대율 150% 환경만 글자가 잘린다 매니페스트의 DPI 설정과 고정 좌표 그리기. 28장

MFC의 결함은 C++ 문법만 봐서는 모르는 경우가 있어, Windows의 메시지, 리소스, 핸들, 모듈, 런타임 설정까지 포함해 봐야 합니다.

36. 신규 개발에서 MFC를 골라야 하는가

완전한 신규 개발에서 MFC를 고를지는 신중히 판단하는 편이 좋습니다.

MFC를 고를 이유가 있다면 이런 경우입니다.

기존 MFC 코드와 밀접하게 연동해야 한다
기존 MFC 부품이나 화면을 재사용하고 싶다
Win32/GDI/COM에 매우 가까운 제어가 필요하다
사내에 MFC 유지보수 스킬이 충분하다
대상이 Windows 데스크톱으로 한정되어 있다
장기 이행 계획상 우선 MFC로 증축해야 한다

반대로 다음의 경우에는 다른 선택지를 검토하는 편이 좋습니다.

모던한 UI를 만들고 싶다
유연한 레이아웃이나 애니메이션이 필요하다
Web 연동이나 클라우드 연동이 중심이다
테스트 용이성을 중시하고 싶다
주니어 개발자가 참여하기 쉬운 기술을 고르고 싶다
크로스 플랫폼 대응이 필요하다
접근성이나 고 DPI를 처음부터 중시하고 싶다

MFC는 「지금부터 배울 가치가 없는」 기술도 「새롭다고 고르는」 기술도 아니며, 기존 Windows 네이티브 자산과 마주하기 위한 기술입니다.

37. MFC에서 이행할 때의 사고방식

MFC 앱을 다른 기술로 이행하고 싶을 때, 처음부터 전면 개편을 목표로 하면 실패하기 쉽습니다.

먼저 앱을 이렇게 분해해 생각합니다.

UI
비즈니스 로직
파일 형식
통신 처리
데이터베이스 처리
디바이스 제어
인쇄
COM/OLE 연동
설정 관리
로그

이 중 가장 MFC에 의존하는 것은 UI입니다.

한편 비즈니스 로직이나 파일 처리는 잘라 낼 가능성이 있습니다.

이행의 현실적인 순서는 이렇게 됩니다.

1. 빌드 환경을 재현한다
2. 기존 동작을 테스트 데이터로 고정한다
3. UI 이벤트 핸들러에서 로직을 잘라 낸다
4. 비MFC C++ 라이브러리로 모은다
5. 자동 테스트를 추가한다
6. 외부 사양을 문서화한다
7. 필요한 화면부터 단계적으로 교체한다

「MFC를 그만둔다」 자체를 목적으로 하기보다 「MFC 안에 갇혀 있는 중요 로직을 밖으로 꺼낸다」를 목적으로 하는 편이 성공하기 쉽습니다.

38. MFC 코드를 읽을 때의 입구

기존 MFC 프로젝트를 처음 읽을 때는 이 부근의 파일부터 봅니다.

*.vcxproj
  도구 집합, MFC 설정, 문자 집합, 런타임 설정을 본다

resource.h
  리소스 ID를 본다

*.rc
  대화 상자, 메뉴, 문자열, 아이콘을 본다

*App.cpp / *App.h
  CWinApp 파생 클래스와 InitInstance를 본다

MainFrm.cpp / MainFrm.h
  메인 프레임과 메뉴/도구 모음을 본다

*Doc.cpp / *Doc.h
  Document/View라면 데이터 구조와 저장 처리를 본다

*View.cpp / *View.h
  그리기와 사용자 조작을 본다

*Dlg.cpp / *Dlg.h
  대화 상자, DDX, 버튼 처리를 본다

다음으로 자주 쓰는 검색 키워드입니다.

BEGIN_MESSAGE_MAP
ON_COMMAND
ON_UPDATE_COMMAND_UI
ON_BN_CLICKED
DoDataExchange
UpdateData
OnInitDialog
OnDraw
Serialize
AfxMessageBox
AfxBeginThread
AFX_MANAGE_STATE

이 부근을 검색하면 애플리케이션의 동작이 보이기 쉬워집니다.

39. MFC를 유지보수할 때의 설계 방침

MFC 기존 자산을 오래 유지보수한다면 이런 방침이 유효합니다.

UI 이벤트 핸들러를 얇게 한다
CString이나 CWnd를 비UI층으로 너무 흘리지 않는다
비즈니스 로직을 일반적인 C++ 클래스로 옮긴다
파일 형식의 호환성 테스트를 만든다
x86/x64 차이를 명확히 한다
리소스 ID 변경을 리뷰한다
MFC DLL의 모듈 상태를 확인한다
로그를 정비한다
CI에서 빌드를 고정한다
고 DPI나 Windows 11에서의 화면 확인을 정기적으로 한다

특히 CDialogCView에 처리를 너무 채워 넣지 않는 것이 중요합니다.

MFC의 화면 클래스는 입력, 표시, 이벤트 전달에 집중시킵니다.

화면에서 값을 가져온다
서비스로 넘긴다
결과를 화면에 반영한다

이 정도로 유지되면 MFC여도 상당히 유지보수하기 쉬워집니다.

40. 실무 체크리스트

MFC 앱을 다룰 때의 체크리스트입니다.

Visual Studio 버전은 명확한가
MFC/ATL 컴포넌트는 설치되어 있는가
x86/x64 대상은 명확한가
Unicode/MBCS 설정은 파악하고 있는가
MFC는 정적 링크인가 공유 DLL인가
필요한 Visual C++ Redistributable은 무엇인가
resource.h와 .rc를 리뷰 대상으로 하고 있는가
메시지 맵으로 이벤트 경로를 따라가고 있는가
UpdateData의 방향은 올바른가
Document/View 구조를 사용하는가
모달리스 대화 상자의 수명 관리는 안전한가
워커 스레드에서 UI를 직접 건드리지 않았는가
MFC DLL에서 AFX_MANAGE_STATE가 필요한 곳은 없는가
고 DPI 환경에서 화면을 확인하고 있는가
COM/ActiveX 의존은 32bit/64bit에 대응하는가
로그는 남는가
비UI 로직을 테스트할 수 있는가

MFC는 익숙해지기 전까지는 독특해 보이지만, 볼 곳이 보이면 상당히 규칙적으로 읽을 수 있습니다.

41. 정리

MFC는 Windows 네이티브 데스크톱 애플리케이션을 C++로 구축하기 위한 역사 있는 프레임워크입니다. 신규 개발의 주류는 아니지만, 기존 자산의 유지보수, 빌드 환경 갱신, 기능 추가, 단계적 이행에서는 지금도 중요한 기술입니다.

MFC를 이해하는 데 특히 중요한 것은 다음입니다.

MFC는 Win32 API를 C++로 다루기 쉽게 한 것이다
CWinApp가 앱 전체를 관리한다
CWnd와 HWND의 수명은 같지 않다
메시지 맵으로 이벤트와 함수가 연결된다
Document/View는 데이터와 표시를 나누는 구조이다
DDX/DDV는 대화 상자의 값 동기화와 검증에 쓴다
리소스 파일과 resource.h가 매우 중요하다
CString이나 TCHAR는 문자 코드 설정과 세트로 이해한다
MFC DLL에서는 모듈 상태에 주의한다
오래된 MFC 앱은 고 DPI, x64, CI, 테스트 관점에서 다시 볼 가치가 있다

MFC를 다룰 때는 「오래됐으니 나쁘다」고 단정하기보다 먼저 구조를 이해하는 것이 중요합니다. 기존 MFC 앱에는 오랜 업무 지식, 고객별 사양, 디바이스 연동, 파일 호환성이 담겨 있는 경우가 있습니다. 그 가치를 지키면서 조금씩 유지보수하기 쉽게 하려면, MFC 관례를 이해한 다음 비 UI 로직을 잘라 내고, 빌드와 테스트를 갖추는 것이 현실적입니다.

굳이 한 마디로 정리하면 이렇게 됩니다.

Windows 네이티브 앱의 구조를 C++ 클래스와 프레임워크로 다루기 위한 기반.

이 관점을 가지면 MFC는 단순한 오래된 기술이 아니라, 기존 Windows 자산을 안전하게 읽어 내기 위한 실마리가 됩니다.

참고

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

MFC란 무엇입니까?
MFC는 Microsoft Foundation Classes의 약자로, Win32 API를 C++ 클래스로 다루기 쉽게 만든 Windows 애플리케이션 프레임워크입니다. 창은 CWnd, 대화 상자는 CDialog, 애플리케이션 전체는 CWinApp와 같은 클래스로 표현됩니다. 「Windows를 몰라도 작성할 수 있는 마법의 라이브러리」가 아니라, Windows의 구조를 C++ 타입과 프레임워크로 정리한 것이므로 Windows 메시지와 핸들 등 Win32 지식도 필요합니다.
MFC는 지금도 사용할 수 있습니까? 지원은 계속됩니까?
MFC는 지금도 Visual Studio에서 사용할 수 있으며, 계속 지원됩니다. 다만 Microsoft 문서에는 새 기능 추가나 문서 갱신은 이루어지지 않는다는 주석이 보입니다. 자리매김으로는 기존 MFC 앱의 유지보수나 기능 추가, 빌드 환경 갱신, 다른 UI로의 단계적 이행에서는 현실적으로 흔한 반면, 완전 신규의 일반적인 GUI 앱에서 채택할지는 신중히 판단해야 하는 기술입니다. Visual Studio Installer의 개별 컴포넌트(C++ MFC for latest build tools) 설치가 필요합니다.
MFC의 메시지 맵이란 무엇입니까?
Windows 메시지와 명령을 핸들러 함수에 연결하는 MFC 메커니즘입니다. BEGIN_MESSAGE_MAP부터 END_MESSAGE_MAP 사이에 ON_BN_CLICKED나 ON_COMMAND 같은 매크로로 「이 버튼이 클릭되면 이 함수를 호출한다」는 대응을 기술합니다. 함수를 검색해도 직접 호출이 보이지 않는데 이벤트 시점에 실행되는 경우에는 메시지 맵을 확인합니다. MFC 코드 리뷰에서는 핸들러 함수뿐 아니라 메시지 맵과 세트로 확인하는 것이 중요합니다.
MFC에서 다른 기술로 이행하려면 어떻게 하면 됩니까?
처음부터 전면 개편을 목표로 하면 실패하기 쉽습니다. 현실적인 순서는 빌드 환경을 재현하고, 기존 동작을 테스트 데이터로 고정하고, UI 이벤트 핸들러에서 로직을 분리해 비MFC C++ 라이브러리로 모으고, 자동 테스트를 추가하고, 외부 사양을 문서화한 뒤, 필요한 화면부터 단계적으로 교체하는 흐름입니다. 「MFC를 그만둔다」 자체를 목적으로 하기보다 「MFC 안에 갇혀 있는 중요 로직을 밖으로 꺼낸다」를 목적으로 하는 편이 성공하기 쉽습니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기