원본 : http://www.codeproject.com/wtl/wtl4mfc1.asp

Readme.TXT

이 문서의 의견 게시판으로 메시지를 보내기전에 먼저 이 문서를 읽어주시기 바랍니다.

제일 먼저 Platform SDK가 필요합니다. 이 SDK가 없으면 WTL을 사용할 수 없습니다. Platform SDK는 온라인 SDK 업데이트 사이트를 이용하거나, 따로 CAB 파일등을 다운 로드받아 직접 설치를 하셔도 됩니다. 꼭 확인 해야 되는 것은 VC 옵션안에 있는 include 와 lib의 VC 검색 디렉토리를 등록해주는 유틸리티를 꼭 실행해주셔야 됩니다. 자동 등록해주는 프로그램은 Platform SDK 프로그램 그룹안에 Visual Studio Registration이 바로 그 프로그램 입니다.

WTL 필요하다면 마이크로소프트에서 버젼 7을 다운로드 받으시기 바랍니다. 더 자세한 설명이 필요하시면 "Introduction to WTL - Part 1"과 "Easy installation of WTL" 문서를 보시면 됩니다.  그 문서들은 세월이 흐른 오랜된 문서일지는 모르겠지만, 나름대로 훌륭한 정보들을 담고 있습니다. 그 중 몇가지 중 하나가 WTL include 디렉토리를 VC상에서 어떻게 연결 시키는지에 대해 언급해 놓고 있습니다. VC 6에서 한다고 하면 Tools|Options 메뉴를 선택 한 뒤 Directories 탭으로 가서 콤보 박스를 이용해 Show directories를 고른 후, Include files를 선택해 주시기 바랍니다.  그리고 난뒤 새로운 항목을 추가해서 WTL 헤더 파일들이 어디에 있는지를 지정해 주시면 됩니다.

먼저 MFC에 대해 어느정도 습득하고 있어야 합니다. 먼저 메시지 맵 매크로에 대해 충분히 이해 할 수 있어야 됩니다. 즉, DO NOT EDIT 라고 적혀진 코드를 망설임 없이 수정할 수 있으며 별 문제가 없어야 된다는 거죠.

그리고 Win32 API 프로그래밍에 대해서도 잘 알고 익숙하게 다룰 수 있어야 합니다. 혹시 윈도우 프로그래밍 공부를 MFC부터 시작해서 API 단계에서 메시지가 어떻게 작동하는지 잘 모른다면, WTL에서 많이 헤맬 수 있습니다. 게다가, 메시지의 WPARAMLPARAM에 대한 의미를 전혀 모른다면 차라리 다른 문서들(CodeProject 안에도 상당히 많습니다.)을 읽고 이해하고 들어가는 것이 좋을듯 싶습니다.

마지막으로 C++ template 문법을 알고 있어야 합니다. 이 부분은 C++ FAQs와 template FAQs에 연결된 VC Forum FAQ를 이용하시면 많은 참고가 될 겁니다.

이 후에 제가 모든 설명은 VC 6기준으로 작성되겠지만, 최소한 제가 아는 범위에서는 VC 7에서도 잘 동작하는 것으로 알고 있습니다. 물론 VC 7을 사용해 본적이 없어 VC 7상에서 발생된 문제를 직접적으로 도와드리긴 어려울지 모르겠습니다. 하지만 VC 7자체의 문제가 아니고 WTL에 관한 문제라면 얼마든지 도와 드릴 수 있습니다.

연재하기에 앞서..

WTL을 굳이 한마디로 표현하면 단단한 돌이라고 할까요? WTL은 MFC같은 수많은 GUI 클래스들을 가지고 있지만, 실제 결과물은 요점 정리되듯 최소화 되서 만들어 집니다. 물론 제가 만든 예제들을 여러분께서는 지금까지 익혀오신 MFC를 사용하여 작성한다면 MFC에서 제공되는 랩퍼 클래스로 손쉽게 작성할 수 있게 되고 메시지 처리도 편하고 간단하고 얼마든지 수정 가능하도록 작성하실 수 있읍니다. 그러나 몇가지 구현하지도 않았는데, 알게 모르게 수백K의 용량이 저절로 붙어 버립니다. 이런 것이 싫다면 바로 WTL을 선택하시면 정답이 될 수 있읍니다. 물론 그 전에 WTL이 가진 약점 몇가지는 꼭 염두해 두시기 바랍니다. WTL이 가진 약점들은 다음과 같습니다.

- ATL 스타일 템플릿은 맨 처음 접해보신 분이라면 겁부터 먹게 만들어 줍니다.
- ClassWizard의 기능은 전혀 쓸 수가 없습니다. 결국 메시지맵을 작성하려면 메뉴얼 부터 붙잡고 있어야 됩니다.
- MSDN에 문서화 되지도 않았습니다. 도움말은 다른 곳에서 찾아야 되고 그것마저 여의치 않다면 WTL로 작성된 소스를 보셔야 될겁니다.
- 아마 서고에 꽂아놓고 볼만 한 참고 도서를 찾기가 수월치 않습니다.
- "Microsoft에서 공식적으로 지원하지 않습니다" 라는 문구가 졸졸 따라 다닐 겁니다.
- ATL/WTL 윈도우 작업은 확실히 MFC 윈도우 작업과는 전혀 틀리기 때문에, MFC로 작업하신 분은 자신이 알고 있는 지식을 훌쩍 뛰어 넘는 것을 확인하시게 됩니다.

그럼 이제는 WTL의 장점을 살펴 보도록 하죠.

- 더 이상 복잡스러운 Doc/View 구조에 대해 고민 하실 필요가 전혀 없습니다.
- MFC에 구현된 기초적인 UI적 특징을 가지고 있으며, DDX/DDV 와 "update command UI" 기능을 포함하고 있습니다.
- 실제적으로 MFC의 기능보다 향상된 것도 가지고 있습니다. (예를 들면 좀 더 유연한 구조의 splitter windows)
- Static-Lincked MFC 프로그램 보다 훨씬 작아진 실행파일을 생성합니다.
- WTL 자체 버그 수정도 가능하기 때문에, 일일히 패치를 기다리며 고민할 필요가 없습니다.(버그 내용이 MFC/CRT DLL안에 있게 되면, 결국 이 문제는 Microsoft가 패치를 내놓지 않는한 다른 프로그램에서도 계속 발생할 수 있게 됩니다.)
어떻게든 MFC가 필요하다면, MFC 와 ATL/WTL 윈도우 작업은 아무런 문제없이 겸해서 작동 시킬 수 있습니다.( 저 같은 경우에는 프로토타입 제작하는 동안에 MFC의 CDialog를 띄워주는 WTL SplitterWindow를 가진 MFC CFrameWnd를 생성해 본적 이 있습니다. -- 물론 시연이 끝났을때는 전부 WTL 코드로 수정해 버렸죠)  

현재 이 시리즈는 ATL 윈도우 클래스에 의해 만들어지게 되었습니다. 여기에 ATL 클래스에서 추가하고 추가해서 현재까지 버젼이 탄생했는데, ATL 윈도우 작업에 대해서 잘 알고 있다면 훨씬 수월한 작업이 될 수 있습니다. 저도 이렇게 전체적으로 ATL에 대해 공부하고 난뒤에는 WTL에 대한 접근이나 GUI 프로그래밍 할 때 훨씬 수월하게 작업할 수 있게 되었습니다.

Part I에 대해

WTL은 상당히 매력적입니다. 하지만 무엇보다 선행되야 하는 건 ATL에 대한 전반적인 이해 입니다. WTL은 ATL 클래스를 모아서 추가시킨 셋이기 때문에, MFC로만 작업하신 분이라면 ATL의 GUI 클래스들의 압박을 이겨 낼 수 있어야 됩니다. 그렇기 때문에 막바로 WTL의 사용하기 전에 저와 함께 끈기를 가지고 ATL 속으로 빠져드는 작업을 해주셔야 합니다.

첫번째 Part에서는 ATL의 약간의 배경 지식을 먼저 소개 하고, ATL 코드를 작성하기 전에 기초적인 몇가지 사항을 짚어 보고, 웃기는 ATL 템플릿에 대해 빠르게 설명하고, 기초적인 ATL 윈도우 클래스들을 살펴 볼 것입니다.

ATL의 배경

ATL과 WTL 역사
The Active Template Library... 묘한 이름이지 않나요? 예전 분들 같은 경우에는 ActiveX Template Library 라고 기억하신 분들도 있을지 모르겠군요. 사실 그 표현이 더 정확하게 표기 한 것입니다. 왜냐면 ATL의 목적은 COM 개체와 ActiveX 컨트롤을 보다 쉽게 작성할 수 있도록 만들어 놓은 것이기 때문입니다. (ATL은 당시에 Microsoft 에서 새로운 ActiveX 제품을 만들 때 붙여진 이름이기 때문에 그렇게 쓰였다가 요즘 .NET 제품들을 뽑아내려다 보니 바뀌었다고 생각하셔도 될겁니다.) 보통 ATL은 MFC에 있는 CWnd와 CDialog 형태의 기초적인 GUI를 구현한 클래스들을 가지고 있어서 COM 개체를 작성할 때 많이 쓰였습니다. 다행히, GUI 클래스들은 WTL 같은 것을을 만들 수 있을 만큼 유연한 구조를 갖추고 있었죠.

WTL은 현재 두번째 큰 개조가 있었습니다. 첫번재는 3.1 버젼에서 였고, 두번재는 7 버젼에서 였습니다 .( 버젼넘버는 ATL 버젼에 따라 선택된 것이기 때문에, 일반적으로 하는 1, 2,.. 이런식으로 나가지 않습니다.) 3.1 버전에서는 VC6과 VC7에서 작동되는데, 단 VC7에서는 VC7에 맞추어진 별도의 선행처리기용 선언문들을 통해 동작했습니다. WTL 7 버젼은 3.1 버젼에서 대체되어 구성되었습니다. 그래서 VC 7에서 별도의 선행처리 작업이 필요 없게 되었습니다. 그래서, WTL을 이용하여 개발할 때 더 이상 3.1을 사용할 필요가 없어졌습니다.
ATL-style template

C++ 템플릿에 대해 어느정도의 경지에 오르신 분들도 주의할 사항이 있습니다. ATL 속에 숨겨진 함정이 두가지가 있기 때문입니다.
먼저 다음 클래스를 보시기 바랍니다.
class CMyWnd : public CWindowImpl< CMyWnd >
{
...
};

실제적으로는 맞는 코드 입니다. C++의 규칙대로 해석하자면 CMyWnd 클래스(정확히는 class CMyWnd 뒷 부분을 보시면 됩니다)는 상위의 템플릿에게서 상속받고 선언되었음을 의미합니다. 템플릿 파라미터에 들어갈 클래스 이름을 넣었기 때문인데, 이부분에서 두번째의 ATL의 함정이 있기 때문입니다. 결론을 부터 말하자면 컴파일 될때, 버추얼 함수를 호출을 합니다.

template < class T >
class B1
{
public:
    void SayHi()
    {
    T* pT = static_cast< T* >(this);   // 허걱? 이 부분을 아래에 설명하겠습니다.

        pT->PrintClassName();
    }
protected:
    void PrintClassName() { cout << "This is B1"; }
};

class D1 : public B1< D1 >
{
    // 전체적으로 오버라이딩 된 함수가 없습니다.
};

class D2 : public B1< D2 >
{
protected:
    void PrintClassName() { cout << "This is D2"; }
};

main()
{
D1 d1;
D2 d2;

    d1.SayHi();    // prints "This is B1"
    d2.SayHi();    // prints "This is D2"
}

static_cast(this)에 그 숨겨진 속셈이 있습니다. 타입 B1* 인 this를 캐스팅 하면서 D1*이나 D2* 형태로 this 즉 B1* 형을 캐스트 하게 되면 호출 시에 특수하게 호출됩니다.
왜냐면 템플릿 코드는 컴파일시에 코드가 생성되기 때문에, 이런 캐스팅에 안정성을 보장 받을 수 있게 되어 올바른 상속받은 리스트로 쓰여지게 됩니다.
하지만, 다음과 같은 쓰게 된다면 문제가 발생 할 수 있습니다.
 class D3 : public B1< D2 > 

this은 D1*과 D2* 형에 대해서만 안정성을 보장 받을 수 있고 그 외에 것에서는 해당되지 않습니다. 주의할점은 이 방법에서 적용된 방법은 일반적인 C++ 다형성과 거의 동일한 형태이기 때문에, SayHi() 메소드는 가상함수가 되지 않습니다.

이게 어떻게 작동하는지 자세하게 설명하기 위해서는 먼저 SayHi()를 호출하는 것 부터 보아야 합니다. 제일 처음 호출 되면, 특수화 된 B1이 사용됩니다. 그리고 SayHi() 코드는 다음과 같이 표현됩니다.
 void B1< D1 >::SayHi()
{
D1* pT = static_cast< D1* >(this);

    pT->PrintClassName();
}

D1이 PrintClassName()을 override를 하지 않았기 때문에, D1의 base class에서 그 메소드를 찾게 됩니다. B1은 PrintClassName() 메소드를 가지고 있기 때문에, 일단 그것을 호출하게 됩니다.
자 이젠, 두번째 SayHi()를 살펴보도록 하죠. 이 때는 특수화 된 B1를 사용하게 되고 이를 완전한 코드 형태로 만들게 되면 다음과 같이 표현됩니다.
 void B1< D2 >::SayHi()
{
D2* pT = static_cast< D2* >(this);

    pT->PrintClassName();
}

이때, D2는 PrintClassName() 메소드를 가지고 있기 때문에, 바로 그 함수가 실행되게 됩니다.

이 기술을 사용하게 되면 얻는 이점을 살펴보도록 하죠
- 객체의 포인터 사용이 별 필요 없게 됩니다.
- 가상 함수 테이블이 필요없으므로 메모리가 절약 됩니다
- 초기화 되지 않은 가상 함수 테이블로 인해, 실행시에 널 포인터로 된 가상 함수를 호출하는 것이 가능합니다.
- 모든 함수는 컴파일시에 표현 되기 때문에, 자체적으로 최적화를 갖출 수 있게 됩니다.

가상 함수 테이블로 얻게 되는 장점은 이 예제에서는 그다지 특이해 보이지 않을지 모릅니다.(측정해 보면 고작 4 byte 정도 밖에 안됩니다.)
하지만 15개의 base class에, 각기 20개 이상의 메소드를 가지고 있다고 생각해 보세요. 상당한 메모리 절약을 얻을 수 있습니다.


ATL 윈도우 클래스들


네 이정도면 충분히 배경 설명이 된것 같습니다. 이제 ATL 세계에 본격적으로 빠져 보도록 하죠. ATL은 정확히 인터페이스/구현 부를 나누어 설계되어 있기 때문에, 윈도우 클래스에서 역시 정확히 나누어져 있습니다. 이 점은 COM과 유사하게 되어 있습니다.COM 역시 인터페이스 선언은 구현 부분과는 완벽하게 나뉘어져 있죠.(물론 몇가지 구현은 제외되지만..)

ATL은 윈도우를 위해 정의된 "인터페이스" 클래스를 가지고 있습니다. 그것을 통해 윈도우 작업을 수행할 수 있죠. 그 클래스는 CWindow라고 불립니다.특이한 클래스라기 보다는 단지 HWND를 랩핑한 수준의 클래스입니다. HWND는 일반적인 API 사용자라면 익숙하게 사용하고, 제일 처음 접하게 되는 파라미터죠. SetWindowText()나 DestoryWindow() 함수만 봐도 알 수 있죠. CWindow는 m_hWnd를 public member로 가지고 있기에 HWND가 필요하다면 손쉽게 접근이 가능합니다. 또 CWindow는 operator HWND 메소드를 가지고 있기 때문에, HWND를 손쉽게 CWindow로 전달 할 수 있습니다. 하지만 CWnd::GetSafeHwnd()와는 전혀 다른 값입니다.

CWindow는 MFC의 CWnd와는 거의 완벽할 정도로 틀립니다. CWindow 객체는 단 한개의 데이터 member를 가지고 있기 때문에, 생성하는데 별 비용이 들지도 않고, MFC에서 내부적으로 HWND와 CWnd를 매핑 하는 객체 맵과 전혀 틀립니다. 당연히 CWnd와는 전혀 다른 범주에 있기 때문에, 관련된 윈도우를 파괴하는 일 따윈 없습니다. 다시 말하자면 임의로 생성한 CWindow 객체를 일일히 detach 할 필요가 없다는 의미입니다.

ATL의 Window 구현 부분은 CWindowImpl에서 담당하게 됩니다. CWindowImpl에는 Window Class 등록과 같은 작업이나 윈도우 서브클래싱, 메시지 맵, 기본적인 WindowPoc()과 같은 기본적인 코드를 담고 있습니다. 당연히 CWnd와 같이 하나의 클래스에서 모두 끝내는 MFC와는 전혀 틀립니다.

또 대화 상자를 구현하는 부분도 CDialogImpl 과 CAxDialogImpl, 두가지 가 있습니다. CDialogImpl은 일반적으로 사용되는 보통 대화상자를 구현할 때, CAxDialogImpl은 ActiveX 컨트롤 사용하는 대화상자를 구현할때 사용됩니다.

윈도우 생성을 위한 설정


대화상자 형태의 윈도우가 아니라면, CWindowImpl을 상속 받아 생성할 수 있습니다. 새로운 클래스를 생성하기 위해서는 다음 세가지 속성을 가지고 잇어야 합니다.
1. Winow class 기본 설정 내역
2. 메시지 맵
3. 윈도우 속성이라고 일컫는 윈도우 자체가 가진 기본 스타일

Window class 기본 설정 내역이란, DECLARE_WND_CLASS 또는 DECLARE_WND_CLASS_EX 매크로를 사용하는 것을 의미합니다. 이 두가지 define 내용을 다르게 표현한다면, WNDCLASSEX 구조체를 감싸만든 ATL용 구조체인 CWndClassInfo를 구성하는 것입니다.
DECLARE_WND_CLASS는 새 윈도우 클래스 이름을 자동 생성과 동시에 다른 멤버들의 기본값들을 자동적으로 제공하며, DECLARE_WND_CLASS_EX를 사용하게 되면 해당 클래스 스타일과 배경색 까지 설정할 수 있게 됩니다.    
물론 클래스 이름을 NULL로 설정할 수 있는데, 그 때는 ATL에서 자동적으로 이름을 생성해 줍니다.

일단 Window Class 기본 설정 내역은 파면 팔수록 복잡하지만 아래의 소스처럼 쓰면 사실 끝입니다. 일단 여기까지만 하고, 다음 이야기를 계속 하겠습니다.
class CMyWindow : public CWindowImpl
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))
};


그 다음으로 알아볼 것은 바로 메시지 맵입니다.
ATL 메시지 맵은 MFC의 메시지 맵보다 더 단순합니다. ATL의 맵은 거대한 switch 문형에 조금 변형을 가한 정도랄까요?; Switch문형 처럼 우항에 위치한 대응되는 함수를 호출합니다. 메시지 맵의 매크로는 BEGIN_MSG_MAP 과 END_MSG_MAP 이 두개 입니다. 한번 윈도우안에 빈 메시지 맵을 추가해보도록 하죠.
class CMyWindow : public CWindowImpl
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))

    BEGIN_MSG_MAP(CMyWindow)
    END_MSG_MAP()
};

메시지 맵안에 핸들러를 삽입하는 방법은 다음 섹션에서 다루기로 하겠습니다.
마지막으로 윈도우 속성을 적용하는 법을 다루어 보도록 하겠습니다.  
728x90

+ Recent posts