본문 바로가기

Library/Windows Programming

MFC와 ATL/WTL에서의 메세지 전달 메커니즘

C/C++을 사용하여 간단한 Windows Application을 만든다고 할 때, MFC나 ATL과 같은 커다란 라이브러리를 사용하는 것은 달갑지 않은 일이다. MFC로 빌드하여 생성되는 바이너리와 링크되는 DLL의 크기는 무시할 수 없을 정도이며, ATL의 경우 코드의 크기는 매우 작지만 단순히 코드의 크기가 이 글의 주제는 아니다. 중요한 프로젝트에 사용할 일이 아니라면, 사용자 인터페이스가 잘 포장되어 있는 이런 라이브러리를 사용하는 것이 마냥 좋다고는 할 수 없다. 편리한만큼 라이브러리 디자인의 발상과 내부 구현 기법을 지나치기 쉽기 때문이다.

MFC와 ATL은 비슷한 메세지 전달 방식을 가지고 있지만, 그 구현 방법은 매우 다르다. 가장 큰 이유는 MFC가 만들어질 당시에는 C++ 표준이란게 없었기 때문이다. RTTI와 같은 기능은 당시에 존재하지도 않았기 때문에 필요한만큼 자체적으로 구현해야 했고, 템플릿을 디자인에 직접 활용한다는 것은 망상에 가까운 일이었다.

ATL은 이러한 기능들이 어느 정도 자리를 잡은 뒤에 설계가 시작되었고, 상대적으로 MFC보다 훨씬 좋은 디자인을 채용하고 있다. 물론, 지금은 MFC도 내부 구현에서 C++의 발전상을 적용하여 많은 부분들이 깔끔하게 재작성 되었지만, 하위 호환성 때문에 과거의 코드들이 남아 있는 부분도 적지 않다.

예를 들어, MFC는 네임스페이스와 같은 기능을 채용하지 않았기 때문에 Afx라는 접두사가 붙는 전역 함수들이 존재한다. 물론 이 전역 함수들은 충분히 전역 함수로 존재할만한 이유가 있는 것들이다. 문제는 이들 함수들의 이름이 불필요하게 길다는 점인데, 코딩하는 입장에서는 아주 고역이다. 즉, 네임스페이스를 통해 이들 함수와 클래스들이 정리되지 않았기 때문에 단일한 범위에서 이들을 구별하기 위해서 이름들이 불필요하게 길어진 것이다. 더구나 네임스페이스의 부재는 모든 클래스가 단일한 범위에 모여 있어 어떤 클래스가 어떤 기능을 하는지 쉽게 추측하기 어렵게 만든다. 닷넷의 클래스들이 각 네임스페이스에 잘 정리되어 있는 것과 대조적이다.

또, MFC는 각각의 윈도우에게 메세지가 제대로 전달되도록 공통의 단일한 윈도우 프로시저가 전역함수 차원에서 선언되어 있고, MFC가 초기화되는 과정에서 훅을 설치하여 생성된 윈도우로 메세지를 보내는 구조로 되어 있다. C++이라기보다는, C에 훨씬 더 가까운 구조라 할 수 있다. 이것은 어느 정도 당시 현실을 반영한 면도 없진 않지만, C++의 개체지향 특성을 적극 활용하지 못한 결과이다. MFC가 API를 단순히 래핑해 둔 클래스 라이브러리라는 말도 이러한 디자인을 근거로 나오는 이야기다.

ATL은 C++에 맞는 개체지향적인 디자인을 적용하였으며, 템플릿을 적절하게 활용하였다. 특히, 일반적인 상속 매커니즘이 아닌, 템플릿을 활용하여 상속에 따른 오버헤드를 제거하는 교묘한 기법은 시대적으로 봤을 때 매우 과감하고 참신한 기법이었다. 예를 들어, ATL에서는 전역 차원에서의 윈도우 프로시저가 없다. MFC가 기본적인 하나의 윈도우 프로시저를 윈도우 클래스에 등록하고, 이 윈도우 프로시저로 오는 메세지를 훅을 통해 메세지를 전파하는 구조로 작성되어 있는 것에 비해, ATL은 클래스에서 static으로 선언된 윈도우 프로시저가 있으며, 훅을 설치하는 것과 같은 복잡한 구조를 사용하지 않는다. 훅은 체인처럼 연결되어 있는 구조라 설치한 훅이 많으면 많을수록 이 체인을 통과하는 오버헤드를 무시할 수 없게 된다. ATL에서는 메세지를 처리할 윈도우 클래스 내에서 매크로를 사용하여 메세지 핸들러를 정의하며, 각 클래스 내에 선언된 디폴트 윈도우 프로시저는 이들 메세지 핸들러에 메세지를 전달하게 된다. MFC에서 미리 정의된 메세지 핸들러를 호출하는 것에 비해 훨씬 더 직관적인 방법이며, 성능면에서도 MFC의 메세지 전달 메커니즘보다 훨씬 효율이 좋다. 물론, static으로 선언된 함수는 (__thiscall)을 통해 호출되는게 아니기 때문에 일반 C 함수와 다를 바가 없지만, 클래스의 선언 범위 내에서 보호를 받는다는 점에서 차이가 있다. MFC와 ATL은 이처럼 비슷한 메세지 전달 구조를 가지지만, 실제 구현은 상이한 면이 있다.

WTL의 경우, WTL은 완전히 새롭게 구현된 라이브러리가 아니라 ATL의 부족한 윈도우 처리 능력을 확장한 것이다. MFC는 애초에 GUI 라이브러리에서 출발하여 COM, ActiveX 지원을 확장한터라 이 부분에 대한 지원은 매끄럽지 못하다. ATL은 MFC의 이런 약점을 보완하기 위해 개발되었다. 매우 경량이며 COM 지원이 뛰어나지만, 상대적으로 GUI 지원이 미약하다. WTL은 ATL의 장점을 극대화하기 위해 추가적인 확장을 한 것이지, 완전히 새롭게 만든 라이브러리가 아니다. 즉, 메세지 전달과 같은 하부 매커니즘을 독자적으로 구현하지는 않았으며, ATL의 매커니즘을 활용한다.


어쨌든, 이런 부분은 이들 라이브러리를 사용하여 안정적이고 빠르게 코드를 작성하는데 목적이 있다면 알 필요가 없는 부분이다. 그러나 라이브러리 제작에 관심이 있는 사람이라면 지나칠 수 없는 부분이다. 덩치가 큰 라이브러리를 사용하는 것이 달갑지 않고, 직접 입맛대로 라이브러리를 작성하고 싶은 사람이라면 직접 코드를 작성하기에 앞서 이들 라이브러리의 내부 구현을 살펴보는 것이 좋을 것이다.