6.WTL 활용

.Win32

WTL은 단순한 라이브러리일 뿐이므로 딱히 용도가 고정되어 있지 않으며 필요한 모든 곳에서 활용할 수 있다. 프로젝트 자체를 WTL 마법사로 생성하지 않았더라도 일부 코드만 WTL로 작성하는 것도 가능하다.

 

: Win32WTL

먼저 가장 단순한 형태인 Win32 프로젝트에 WTL을 활용해 보자. 새 프로젝트 대화상자에서 Win32/Win32 프로젝트를 선택하고 이름은 Win32WTL로 지정한다.

빈 프로젝트 옵션을 선택한 후 CPP 파일 추가하고 일반적으로 많이 사용되는 시작 파일을 복사해 붙여 넣는다. 시작 파일은 본 사이트에서도 쉽게 구할 수 있는데 ApiStart.cpp라는 형태로 배포된다. WinMain은 건드릴 필요없이 WndProc의 WM_PAINT에만 코드를 작성했다.

 

#include "atlbase.h"

#include "atlapp.h"

#include "atlmisc.h"

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     switch (iMessage) {

     case WM_CREATE:

          hWndMain=hWnd;

          return 0;

     case WM_PAINT:

          {

          CPaintDC dc(hWnd);

 

          CRect rt;

          rt.SetRect(10,10,250,100);

          CString str=TEXT("청산은 나를 보고 말없이 살라 하고")

              TEXT("창공은 나를 잡고 티없이 살라 하네 사랑도 벗어 놓고 미움도 벗어 놓고")

              TEXT("물같이 바람같이 살다가 가라 하네");

          dc.DrawText(str,-1,&rt,DT_WORDBREAK);

          return 0;

          }

     case WM_DESTROY:

          PostQuitMessage(0);

          return 0;

     }

     return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

앞에서 작성했던 WTL 코드를 WM_PAINT 블록에 그대로 붙여 넣었다. 단, 지역 객체는 case블록에서 초기화되지 못하므로 { }로 블록을 감싸야 한다. 이 코드가 제대로 동작하기 위해서는 필요한 헤더 파일을 포함시켜야 한다. atlbase.h와 atlapp.h는 무조건 포함시켜야 하고 그 외 필요한 헤더 파일을 포함하되 이 예제는 CRect, CString 등의 유틸리티 클래스를 사용하므로 atlmisc.h도 포함해야 한다.

그 외 특별한 주의 사항은 없다. WTL의 모든 클래스는 WTL 네임스페이스에 정의되어 있지만 헤더 파일내에 using 썬언이 되어 있으므로 WTL::을 붙이지 않아도 곧바로 사용할 수 있다. 템플릿으로 정의되어 있을 뿐 그냥 C++ 클래스일 뿐이므로 C++ 문법에 맞게만 사용하면 된다.

물론 Win32 프로젝트에서 WTL을 굳이 사용해야 할 이유는 없다. 하지만 WTL로 만든 모듈이나 코드 조각을 재활용하고자 할 때는 언제든지 혼합 프로그래밍이 가능하다. C 형식의 Win32에서도 CString을 쓸 수 있다는 것은 참 멋진 일이다. 하긴 CString이 아니라도 string 같은 멋진 클래스가 C++ 표준에 이미 정의되어 있어 요즘은 공짜로 쓸 수 있는 코드가 널린 셈이다.

.MFC

MFC에서 WTL을 사용하는 것은 Win32에서처럼 쉽지 않다. MFC가 WTL의 슈퍼셋인데 과연 MFC에서 WTL을 쓸 일이 있을까 싶겠지만 실무에서는 있다. 팀 프로젝트를 하다 보면 MFC로 진행중인 프로젝트에 WTL로 만든 컨트롤을 붙여야 하기도 한다. 비슷한 두 라이브러리를 같이 쓰자면 일단은 명칭 충돌이 빈번해서 골치가 아프며 그 외에도 설명하기 어려운 여러 가지 문제가 발생한다.

가급적이면 MFC와 WTL은 같은 프로젝트에서 쓰지 않는 것이 좋다. 프로젝트 초기부터 개발툴을 통일시켜야 하는데 그렇지 못하면 이런 귀찮은 일이 발생한다. 특히 협력 회사간에 공동 프로젝트를 진행할 때 이런 일이 빈번하다. 바람직하지는 않지만 MFC와 WTL을 같이 사용해야 할 불가피한 경우가 있으므로 어떤 사항을 주의해야 하는지 프로젝트를 만들어 보자.

 

: MFCWTL

SDI 옵션만 선택하고 나머지는 모두 디폴트 옵션을 취한다. 복잡한 코드 쓸 필요없이 마우스 왼쪽 버튼 누를 때 타원 하나만 그려 보자. MFC에서는 다음 코드면 된다.

 

void CMFCWTLView::OnLButtonDown(UINT nFlags, CPoint point)

{

     CClientDC dc(this);

     dc.Ellipse(point.x-16, point.y-16, point.x+16, point.y+16);

}

 

WTL의 코드도 비슷하지만 CClientDC의 생성자로 전달되는 것이 객체가 아니라 윈도우 핸들이라는 점이 다르다. WTL을 쓰기 위해 헤더 파일은 인클루드해야 한다.

 

#include "atlbase.h"

#include "atlapp.h"

void CMFCWTLView::OnLButtonDown(UINT nFlags, CPoint point)

{

     CClientDC dc(m_hWnd);

     dc.Ellipse(point.x-16, point.y-16, point.x+16, point.y+16);

}

 

컴파일해 보면 CClientDC가 모호하다는 에러가 발생하며 생성자의 인수로 핸들을 넘길 수 없다는 에러도 발생한다. 이 클래스가 MFC에서도 있고 WTL에도 있기 때문에 어느 라이브러리의 클래스를 사용해야 할지 컴파일러가 정신을 차리지 못한다. 이 외에 CString, CBrush 등의 명칭들도 사용하면 모호하다는 에러가 발생한다. 이 모든 문제는 명칭 충돌에서 발생한 것이다. WTL 헤더 파일 atlapp.h를 보면 다음 코드가 있다.

 

#ifndef _WTL_NO_AUTOMATIC_NAMESPACE

using namespace WTL;

#endif // !_WTL_NO_AUTOMATIC_NAMESPACE

 

헤더 파일에서 WTL 네임스페이스를 using 선언하으므로 WTL의 모든 명칭이 전역 네임스페이스로 이동하며 따라서 MFC의 명칭과 혼란이 발생한 것이다. 매크로 구문에서 보다시피 using 선언은 특정 매크로가 선언되어 있지 않을 때만 수행된다. 이 매크로를 선언하면 using 선언을 하지 않을 것이다.

 

#define _WTL_NO_AUTOMATIC_NAMESPACE

#include "atlbase.h"

#include "atlapp.h"

void CMFCWTLView::OnLButtonDown(UINT nFlags, CPoint point)

{

     CClientDC dc(m_hWnd);

     dc.Ellipse(point.x-16, point.y-16, point.x+16, point.y+16);

}

 

그러나 이렇게 하면 명칭 충돌 문제는 해결 되지만 정작 WTL의 CClientDC 클래스를 찾지 못한다. 어쩔 수 없이 CClientDC앞에 소속을 밝혀야 한다. 다음 코드는 잘 동작한다.

 

#define _WTL_NO_AUTOMATIC_NAMESPACE

#include "atlbase.h"

#include "atlapp.h"

void CMFCWTLView::OnLButtonDown(UINT nFlags, CPoint point)

{

     WTL::CClientDC dc(m_hWnd);

     dc.Ellipse(point.x-16, point.y-16, point.x+16, point.y+16);

}

 

중복된 명칭에 WTL::을 붙여 WTL 라이브러리의 명칭임을 분명히 했다. 요렇게 하면 해결된다. CClientDC 라고 쓰면 MFC의 CClientDC이고 WTL::CClientDC라고 쓰면 WTL의 CClientDC이다. WTL의 주이고 MFC가 부라면 반대 방법을 쓸 수도 있다. using namespace WTL를 쓰고 모든 MFC 명칭앞에 ::을 붙여 전역 명칭임을 명시하면 된다. 그러나 누가 봐도 WTL::을 붙이는 것이 더 간편하다.

WTL 개발자는 의도적으로 MFC의 클래스와 같은 명칭을 사용했다. 그 편이 MFC 개발자들에게 편하게 느껴지기 때문이다. wCPen, wCString이라는 명칭을 사용했다면 충돌은 피할 수 있지만 아무래도 어색하다. 정 충돌이 발생하면 네임스페이스라는 장치를 쓸 수 있으므로 큰 문제가 되지 않을 것이라고 생각한 것이다. 네임 스페이스라는 문법은 이럴 경우 확실히 도움이 되기는 한다.

그러나 막상 대규모의 MFC 프로젝트에 WTL 코드를 혼합해서 쓰기는 이론처럼 쉽지만은 않다. 여기 저기서 명칭 충돌이 발생하는데 특히 매크로 내에서 명칭이 충돌되면 아주 곤란해진다. 어쨌거나 해결 방법은 있기는 하지만 비용이 많이 든다. 만약 정 두 라이브러리를 같이 사용해야 한다면 소스 차원에서 혼합하는 것보다 한쪽 컨트롤을 DLL로 만들어 쓰는 것이 더 바람직하다.

.모바일 프로젝트

WTL로 모바일 프로그래밍도 가능하다. 최근 CE 기반의 모바일 장비가 많아져 모바일 개발 수요가 증가했는데 이런 프로젝트에 WTL은 꽤 좋은 개발 방법이다. 모바일 개발을 하려면 WTL 설치 폴더의 AppWizMobile 디렉토리에서 setup80.js 스크립트를 실행하여 스마트 장치 프로젝트에 WTL 마법사를 등록해 두어야 한다. 그러면 스마트 장치에 WTL Mobile 마법사가 나타난다.

 

: WTLMobile

이 마법사를 이용하여 프로젝트를 만들어 보자. 스마트 장치 프로젝트에서 WTL Mobile 마법사를 선택하고 이름은 WTLMobile로 준다.

모바일용 마법사는 PC용 마법사와는 모양이 많이 다르다. Platforms 페이지에서 타겟 플랫폼을 지정하는데 이 플랫폼은 설치된 SDK의 종류에 따라 달라진다. 현재 나의 노트북에는 Windows Mobile 6 SDK가 설치되어 있으므로 이 플랫폼이 나타나는데 그렇지 않다면 2003 플랫폼만 나타날 것이다.

WM 6 Professional SDK만 선택하고 Application Type 페이지에서 Generate .cpp Files 옵션을 선택한다. 모바일용 프로젝트이다 보니 전체적인 구조가 PC용 프로젝트와는 상당히 다르며 코딩 방법에도 미세한 차이점들이 많다. 뭔가 출력을 해 보기 위해 뷰에 다음 코드를 작성한다.

 

LRESULT CWTLMobileView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)

{

     CPaintDC dc(m_hWnd);

 

     dc.ExtTextOut(10,10,0,NULL,L"WTL Mobile",lstrlen(L"WTL Mobile"),NULL);

 

     return 0;

}

 

모바일 환경에서는 TextOut이 없으므로 ExtTextOut으로 문자열을 출력해야 한다. 실행하면 에뮬레이터가 나타나고 문자열이 출력될 것이다.

마우스 메시지를 처리하면 모바일 화면에 자유 곡선을 그릴 수도 있고 대화상자를 열거나 컨트롤을 쓸 수도 있다. 물리적인 장비가 있다면 연결된 상태에서 실행 및 디버깅해 볼 수도 있다. 다음은 WM 6.1 장비를 PC에 연결한 채로 이 예제를 실행한 것이다.

자기 핸드폰에서 사용할 프로그램이 자신이 직접 만들어서 사용할 수 있다는 것은 참 멋진 일이다. WTL이 가장 실용적인 분야가 바로 모바일 환경이다. 사실 데스크탑 환경에서는 MFC만으로도 충분하다. 모바일 환경은 아직도 리소스가 넉넉치 않으므로 MFC는 부담스러우며 작고 가벼운 라이브러리가 필요하다.