MFC 는 윈도우에 맞게 설계된 라이브러리를 제공해줍니다 . 기본적인 라이브러리의 기능 , 예를 들면 파일입출력과 관련된 라이브러리 . 데이터구조 라이브러리 뿐만 아니라 응용프로그램 프레임워크까지도 제공해줍니다 . 응용프로그램 프레임워크를 제공해주기 때문에 라이브러리로 응용프로그램의 전체적인 기본 뼈대를 쉽게 구현할 수 있으며 , 따라서 개발자는 응용 프로그램의 실제 기능에 더 집중하여 프로그래밍 할 수 있다는 장점이 있습니다 .
MFC 는 응용 프로그램의 가장 기본적인 기능들을 유도하는 데 사용되는 세 가지 주요클래스 CWinApp, CDocument, CView 를 제공한다 . Cdocument 클래스의 인스턴스는 응용 프로그램이 처리하는 정보를 관리하는 클래스로 응용 프로그램이 처리하는 정보를 저장하고 로드하며 , 메모리에 보관하는 책임을 진다 . CView 클래스의 인스턴스는 Cdocument 가 관리하고 있는 정보를 사용자에게 보여주는 역할을 한다 . 또 사용자로부터 입력받은 것을 Document 로 전달하는 역할도 한다 . CWinApp 클래스의 인스턴스는 기본적으로 응용프로그램이 윈도우에서 작동하려면 등록을 해야하는데 , 이 등록하는 작업을 수행한다 . 또한 프로세스를 동작시키며 , 메인윈도우를 생성해준다 .
CWinApp 의 Base Class 로는 CObject, CCmdTarget, CWinThread 가 있다 . 따라서 CWinApp 는 Base Class 들의 기능들을 가지고 있다 . CWinThread 클래스는 Thread 를 내부적으로 관리 한다 . Thread 에 관해서는 10 장에서 더 자세히 다룬다고 한다 . CCmdTarget 클래스는 MFC 에서 중요한 기능 중 하나인 메시지맵을 구현하는데 중요한 클래스이다 . 이벤트가 발생하면 , 해당 이벤트와 함수를 연결시켜주는 중요한 역할을 한다 . MFC 에서는 API 에서 switch 문으로 이벤트와 메시지를 연결시키던 방식과 C++ 의 주요 기능인 vtable 을 사용하지 않고 메시지 맵을 이용한다 . 그 이유는 API 의 문장은 보기도 힘들고 실제 속도도 느리기 때문이며 , vtable 은 메시지의 개수 만큼의 가상함수가 필요하므로 따라서 결과적으로 메모리 낭비가 심하다 . 그래서 메시지 맵을 사용 한다 .
앞에서 말했던 메시지맵에 구현 방법에 대해 알아보자 . 메시지맵을 구현하기 위해서는 3 개의 매크로가 필요하다 . 첫번째는 , DECLARE_MESSAGE_MAP() 매크로로 메시지 맵의 항목들을 보관하는 배열을 선언한다 . 또한 , 베이스클래스의 메시지 맵의 항목보관 배열을 가리키는 포인터와 자신의 배열을 가리키는 포인터를 선언한다 . 다음으로 BEGIN_MESSAGE_MAP() 과 END_MESSAGE_MAP() 매크로가 있는데 , 이 매크로 사이에 실제로 메시지맵을 구현하기 위한 정보가 입력된다 . BEGIN_MESSAGE_MAP() 에는 인자로 클래스 자신과 자신의 베이스 클래스명이 들어가며 , 이를 통해 메시지맵은 이벤트가 발생했을 때 , 자신 부터 최상위 베이스 클래스까지 해당하는 메시지를 찾아내는 작업을 할 수 있게 된다 . ( 단 , WM_COMMAND 일 때 )
메시지의 전달 메커니즘 SDI 와 MDI 는 구조계층이 조금 다르므로 , 메시지 전달 순서가 다르다 . WM_COMMAND 메시지의 경우 해당클래스 에서 메시지가 처리되지 않을 경우 부모 클래스에게 전달하고 , 부모가 처리하지 않으면 또 그 위 부모 클래스에게 전달 한다 . 만약 , 최상위 클래스에서도 처리하지 않으면 , 윈도우 내에 함수가 호출된다 . 반면에 시스템 메시지는 해당 클래스에서 메시지가 처리되지 않아도 , 전달하지 않는다 .
Cobject 클래스는 MFC 클래스들의 루트 클래스이다 . 이 클래스는 아주 적은 몇가지 기능만을 제공해준다 . 제공해주는 기능으로는 메모리관리 , 디버깅 지원 , 직렬화 , 런타임 정보등이 있다 . 메모리관리 MFC 의 거의 모든 클래스는 Cobject 로부터 유도되므로 Cobject 데이터 타입과 연관된 new 와 delete 연산자를 사용하게 할 수 있다 . 따라서 , 모든 클래스 메모리 요청을 Cobject 로 모을 수 있다 . 중앙에서 메모리를 관리하기 때문에 메모리 누수를 보다 쉽게 막을 수 있다 . ( 예 : 프로그램을 종료할 때 delete 되지 않은 클래스 ) 디버깅 지원 Cobject 는 Dump() 라는 함수를 지원해주는데 Dump() 함수를 통해서 실행도중에 상태 정보를 엑세스 할 수 있다 . 단 , Dump() 함수는 디버그 모드에서만 호출되며 , 릴리즈에서는 호출되지 않는다 . 쉘로우 스냅 쉘로우 스냅이란 개체가 베이스 클래스로 부터 상속받지 않은 멤버의 정보들만 Dump() 함수로 호출해야한다는 것을 의미한다 . ASSERT 어썰트란 MFC 개발자들이 유효성을 테스트하기 쉽게 해주는 매크로를 말한다 . 이 매크로는 앞에 말했던 Dump() 처럼 디버그 모드에서만 활성화 된다 . ASSERT 는 ASSERT 안에 인자값의 결과가 0 이 아니면 , 아무 호출을 하지 않지만 , 인자 값의 결과가 0 이면 경고 대화상자를 보여준다 . [ 취소 ] 를 누르면 응용 프로그램을 종료시키며 , [ 재시도 ] 를 누르면 디버거로 가서 잘못된 정보를 확인할 수 있다 . [ 무시 ] 를 누르면 , 응용프로그램이 계속 실행된다 .
MFC 의 빌드방법에는 여러가지가 있지만 , 그 중에서 Debug Build 와 Release Build 에 대해서 알아보도록 하겠다 . Debug Build 는 개발자에 의해 사용되어야 하는 Build 로 배포되지 않아야 한다 . Debug Build 는 Release Build 에 비해 상대적으로 사이즈가 크다 . Debug Build 는 거의 개발한 것에 대해 특별한 처리를 하지 않은 상태로 ( 있는 그대로로 ) 컴파일을 하지만 , Release Build 는 최적화된 코드로 컴파일을 하기 때문에 사이즈 면에서나 속도면에서 좋다 . 하지만 최적화 과정에서 문제가 발생할 수도 있다 . 예를 들면 , 앞서 말했던 ASSERT 매크로에서 Debug Build 는 ASSERT 매크로를 모두 포함시키지만 , Release Build 는 ASSERT() 의 인자 조차도 포함시키지 않는다 . 따라서 ASSERT 매크로 내에 구현을 위한 소스를 넣을 경우 문제가 발생한다 .
직렬화란 개체를 지속적으로 존속할 수 있도록 하기 위해 , 디스크로 데이터를 쉽게 읽고 쓸 수 있도록 해주는 장치이다 . 사용방법은 아직 구현해보지 못해서 잘 모르겠다 .
RunTime Type Information (RTTI) 를 통해 , 실행시간에 개체 타입의 속성을 알 수 있다 . 예를 들어 A 함수가 B 함수의 유도함수 인지 아닌지를 판단하거나 , C 함수의 부모 함수의 이름을 얻는 등의 작업을 할 수 있다 . DECLARE_DYNAMIC(CClassName) 런타임 타입정보를 사용하기 위해 선언하며 , 이 매크로는 클래스의 선언부에 포함되어야 한다 . IMPLEMENT_DYNAMIC(CClassName, CBaseClassName) 런타임 타입정보를 베이스클래스와 함께 선언하는 것으로 DECLARE 를 한뒤에 클래스 구현부에서 선언해줘야한다 . 어떠한 클래스를 RTTI 정보를 보여주도록 선언한 후 RUNTIME_CLASS 매크로로 감싸면 , 그 클래스를 기술하는 CRuntimeClass 포인터를 얻을 수 있다 . 이러한 방식은 여러곳에 응용되는데 대표적인 예로 도큐먼트 템플릿생성이 있다 .
IsKindOf() 함수를 통해서 동일한 클래스의 인스턴스 인지 아닌지를 판단할 수 있다 . ASSERT_KINDOF() 는 IsKindOf() 와 마찬가지로 동일한 클래스의 인스턴스인지 확인해주는 매크로이다 . 이 매크로는 동일한 클래스의 인스턴스가 아닐 경우 ASSERT 를 발생시킨다 . 또한 ASSERT 매크로와 마찬가지로 릴리즈 빌드 환경에서는 작동하지 않는다 . STATIC_DOWNCAST() 와 DYNAMIC_DOWNCAST() 매크로는 개체의 포인터를 다른 타입의 개체를 가리키는 포인터로 캐스팅해주는 매크로이다 . 결과적으로 STATIC 은 오류발생시 ASSERT() 를 발생시키며 DYNAMIC 은 NULL 값을 리턴한다 . 이 매크로의 인자는 Cobject 유도 클래스에서만 사용가능하다 .