MFC의 구조

 윈도우 프로그래밍 경험이 있다면 MFC를 이해하기가 쉬울 것이다. 만일 윈도우 프로그래밍 경험 중에도 SDK프로그래밍 경험이 있다면 훨씬 더 이해하기 쉬울 것이다. 왜냐하면 MFC는 SDK의 구조를 거의 그대로 클래스로 가져다 만들었기 때문이다. 이제부터 하는 설명은 주로 SDK와 관련한 내용이 많다.
 MFC의 하부 구조가 SDK이고 이번 장은 그 구조를 설명하는 부분이기 때문이다. 따라서 SDK 프로그래밍을 해 본 경험이 없다면 이해하기가 힘들 것이다. 하지만 이 장은 MFC의 내부를 설명하는 부분이므로 몰라도 별 상관은 없지만 내용을 잘 이해한다며 그만큼 프로그래밍을 하기는 쉬울 것이다. 이제부터 SDK를 바탕으로 MFC의 내부 구조가 어떤 식으로 구성되어 있는지 C++을 중심으로 알아보자.

 SDK 윈도우 프로그래밍을 하다보면 핸들이란 말을 많이 듣게 된다. 핸들이란 윈도우, 디바이스 컨텍스트, 비트맵, 메모리 등의 오브젝트라 할 수 있는 것을 지칭하는데 사용된다. 핸들을 어떤 오브젝트를 구별하기 위해 사용하는 일종의 일련번호라고 생각해도 될 듯하다. 예를 들어 윈도우 핸들이란 각 윈도우를 구별하기 위해 윈도우별로 할당된 번호라고 생각하는 것이다. 위도우와 관계된 모든 API에는 조작 대상이 되는 윈도우를 지칭하는 무엇인가가 필요하다. 그래서 그런 API들의 인자를 살펴보면 그런 용도로 사용되는 인자가 있다 .바로 그 인자로 윈도우 핸들이 사용되는 것이다. 예를 들어 윈도우의 크기를 얻고 싶으면 GetClientRect API를 사용하면 된다. 사용법은 다음과 같다.

또 윈도우의 크기나 위치를 변경하고 싶다면 MoveWindow API를 이용하면 된다.

 위의 두 API를 보면 작업대상인 윈도우를 지칭하기 위해 첫 번째 인자로 윈도우 핸들을 입력받고 있음을 알 수 있다. 이런 윈도우 핸들과 윈도우 관련 API를 바탕으로 MFC에서는 CWnd라는 클래스를 생성하였다. CWnd 클래스에는 윈도우와 관련있는 모든 SDK의 함수들이 들어가 있다. 앞의 예에 비추어 보자면 대략 다음과 같은 모양을 띠게 된다.

 위에 CWnd 클래스의 정의를 아주 약간 보였는데 실제 CWnd 클래스의 정의를 보고 싶으면 MSDEV\MFC\INCLUDE 디렉토리에 있는 AFXWIN.H를 보기 바란다. 생각보다 덩치가 굉장히 클 것이다. AFXWIN.H에는 MFC클래스 대부분의 정의가 들어있다.  위의 CWnd 클래스를 보면 데이터 멤버로 m_hWnd 가 있다. 바로 윈도우 핸들이다.
 멤버함수로는 윈도우에게 무엇인가 작업을 할 수 있는 것들이 있기 마련인일텐데 MFC는 그 함수들의 이름을 SDK에서와 같은 이름으로 사용하고 있다. 앞서 살펴본 것처럼 GetClientRect나 MoveWindow 역시 SDK에서와 같은 이름을 그대로 사용하고 있음을 알 수 있다. 그렇기 때문에 SDK에 대한 경험이 있는 사람이 MFC를 배우기가 유리하다는 것이다. 이들 함수는 실제로 어떻게 구현되어 있을까? 다음과 같이 데이터 멤버인 m_hWnd를 이용해 SDK에 있는 윈도우 API를 바로 호출해 버린다.

 CWnd 클래스의 멤버함수들을 보면 SDK 프로그램시에 볼 수 있던 이름들이 많음을 알 수 있는데 모두 SDK에서와는 달리 인자중에 윈도우 핸들이 빠져있음을 알 수 있을 것이다. 그 이유는 위에서처럼 윈도우 핸들은 데이터 멤버로 숨겨 놓고 나중에 이용하기 때문이다.
 이런 방식으로 MFC의 클래스는 구성되어 있다. 즉 MFC의 밑바탕이 SDK라는 말의 의미를 이해할 수 있을 것이다. 위의 예를 보면서 MFC는 SDK 위에 한 겹을 더 쌓아둔 셈이니 속도가 많이 느려지겠구나 라고 생각할지도 모르겠다. 하지만 꼭 그렇지는 않다. 사실 위의 함수들은 인라인 함수로 정의되어 있기 때문에 컴파일에 앞서 매크로처럼 치환되어 버리기 때문이다. MFC에서 직접 SDK API를 부르는 모든 함수는 인라인 함수로 구현되어 있다.  MFC의 인라인 함수는 \MSDEV\MFC\INCLUDE 디렉토리에 가보면 *.inl 이라는 파일들이 있을텐데 거기에 모두 정의되어 있다.

 이번에는 디바이스 컨텍스트를 갖고 예를 들어보자. 디바이스 컨텍스트란 윈도우에서 출력장치에 무엇인가를 출력하고자 하면 반드시 필요하다. 디바이스 컨텍스트란 출력대상을 지칭하는 것이라고 생각하면 된다.
 화면에 무엇인가를 출력하고자 하면 화면에 대한 디바이스 컨텍스트가 필요하고 프린터에 무엇인가 출력하고자 하면 역시 프린터에 대한 디바이스 컨텍스트가 필요하다. 그런데 디바이스 컨텍스트를 생성하면 그것의 번호가 리턴된다. 이를 인자로 출력함수를 호출하면 그리로 출력이 나간다. 예를 들어 화면에 사각형과 텍스트를 출력하고자 한다면 Rectangle 이라는 함수와 TextOut 이란 함수를 이용하면 된다.

 GetDC라는 함수가 바로 윈도우 화면에 대한 디바이스 컨텍스트를 생성하여 그 번호(핸들)를 돌려주는 함수이다. Rectangle과 TextOut과 같이 디바이스 컨텍스트로의 출력에 사용되는 함수를 GDI(Graphic Device Interface) 함수라 하는데 이 함수들의 특징은 출력함수이기 때문에 인자로 출력대상을 지칭하는 디바이스 컨텍스트 핸들이 필요하다는 것이다. MFC에서는 이런 GDI들과 디바이스 컨텍스트 핸들을 CDC라는 클래스 안으로 집어넣어 버렸다. CDC는 다음과 같은 구조를 갖고 있다.

 위의 CDC 클래스의 Rectangle 과 TextOut 멤버함수는 어떤 구조를 갖고 있을까? 어느 정도 감이 잡힌다면 MFC에 존재하는 클래스들의 기본구조는 안 것이나 다름없다. (다만 기본적인 구조일 뿐이지만..) 정답은 다음과 같다.

 CWnd 에서처럼 내부적으로 유지하고 있는 디바이스 컨텍스트 핸들을 인지로 하여 SDK에 있는 API를 그대로 호출하고 있다. 위의 것 역시 인라인 함수로 정의되어 있다.

 결론적으로 MFC의 하부구조는 SDK에 있는 API로 되어 있음을 알 수 있다. 결국 MFC란 SDK API를 기능적으로 구분하여 그 위에 클래스란 옷을 입힌 것이라 할 수 있다. 그 기능적인 분류가 앞서 본 것처럼 핸들을 중심으로 이루어져 있다.
 이는 어디까지나 기본적인 클래스의 경우에 해당하는 것이고 좀더 복잡하고 강력한 기능을 제공하는 클래스들은 이를 바탕을 존재하고 있다.