VISLAB 박제강 1. 시작하기전에영상관련알고리즘을개발하는과정에서작성한프로그램을테스트하고피드백하는작업은빈번하게발생한다. 이때기존콘솔 (Console) 형태로작성된프로그램의경우테스트작업을유동적으로조절할수없기때문에작업의효율이떨어진다. 반면 GUI(Graphical User Interface) 형태로작성된프로그램은사용자가자신의의도를쉽게반영하고그결과를빠르게눈으로확인할수있다는장점이있다. 윈도우즈응용프로그램을개발하는방법에는여러가지가있는데 MFC(Microsoft Foundation Class) 는 C++ 클래스라이브러리형태로제공된다. 기존운영체제가제공하는 Win32 API(Application Programming Interface) 함수를직접사용해서프로그램을작성하는방식의경우프로그램을섬세하게제어할수있고속도도빠르지만방대한양의 API 함수에대해서알아야하고작성해야하는코드의양이많다는단점이있다. MFC는이러한 Win32 API를클래스로랩핑 (Wrapping) 하여직관적인이름을가지는멤버함수형태로제공하기때문에 API 함수들을더쉽게사용할수있다. 또한프로그램의창을구성하는각각의요소 ( 클래스 ) 들의객체를생성하고이를조립해서전체프로그램창을구성하기때문에사용하기쉽고속도도빠르다는장점이있다. 클래스를사용하기위한 C++ 에대한지식과 MFC가제공하는클래스들의구조를일정수준이상이해한다면간단한테스트프로그램정도는쉽게작성할수있다. 2. 윈도우즈응용프로그램 메시지와이벤트 MFC가 Win32 API 함수를멤버함수형태로제공하기때문에 Win32 API 함수를따로공부할필요는없지만 MFC를사용하더라도윈도우즈응용프로그램의기본적인특징은알고있어야한다. 그중에서도윈도우즈응용프로그램은언제발생할지알수없는사용자의입력신호를메시지라는개념을통해입력받는다는점에서기존의콘솔응용프로그램과차이가있다. 콘솔응용프로그램 : 프로그램이작성될때미리입력된일련의명령들을순서대로실행하는순차적실행방법을따른다. 윈도우즈응용프로그램 : 프로그램의실행순서가정해져있지않고사용자의입력과같은특정요인 ( 메시지 ) 에의해실행순서가달라진다. 1
메시지 설명 WM_CREATE 윈도우가처음만들어질때발생한다. WM_DESTROY 윈도우가메모리에서삭제될때발생한다. WM_PAINT 화면을다시그려야할때발생한다. WM_LBUTTONDOWN 마우스왼쪽버튼을눌렀을때발생한다. WM_MOUSEWHEEL 마우스휠을돌렸을때발생한다. WM_KEYDOWN 키보드의시스템키를제외한나머지키를눌렀을때발생한다. 표 1. 자주사용되는윈도우즈메시지 표1에서알수있듯이메시지에는여러가지종류가있지만메시지의개념을이해하기가장좋은예는 WM_KEYDOWN이나 WM_LBUTTONDOWN과같은키보드나마우스의입력과관련된메시지이다. 사용자가키보드의어느버튼을언제누를지는프로그램을작성할때미리알수없는사항이다. 때문에윈도우즈프로그램은사용자가언제어떤버튼을누르는지를항상체크하고있어야한다. 윈도우즈프로그램은이와같은사용자나시스템의내부적인동작에의해발생된변화에대한정보를메시지라는개념으로표현한다. 또한발생된메시지들을순서대로저장해두는공간을메시지큐라고하고메시지큐에저장된메시지를꺼내서처리하는부분을메시지루프라고한다. 메시지가처리되는구조는비쥬얼스튜디오에서윈도우즈응용프로그램프로젝트를생성하면미리작성되어있기때문에프로그래머가직접구현할필요는없다. 프로그램작성자는특정메시지가발생했을때어떤작업을수행할것인지를구현하면된다. 그림 1. 클래스속성창의메시지탭 ( 왼쪽 ), 메시지맵 ( 오른쪽위 ), 메시지와연결된함수 ( 오른쪽아래 ) 그림1은비쥬얼스튜디오에서메시지를등록하거나편집할수있는클래스속성창의메시지탭 ( 왼쪽 ) 과실제코드에메시지와이벤트가등록되는메시지맵 ( 오른쪽위 ), 그리고해당메시지가발생하면실행되는함수 ( 오른쪽아래 ) 를보여주고있다. 메시지맵에등록된메시지가발생하면그에해당하는함수가실행되므로프로그래머는수행할작업내용을함수내부에구현하면된다. 메시지를등록할때프로그램작성자가윈도우즈응용프로그램구조에익숙하다면코드를직접수정하여메시지를등록할수도있지만그렇지않다면클래스속성창의메시지탭 ( 왼쪽 ) 을통해메시지를등록하는것이일반적이다. 2
그림 2. 다이얼로그기반프로그램의실행화면 ( 왼쪽 ), 클래스속성창의이벤트탭 ( 오른쪽 ) 또한그림1의메시지맵을보면 WM_ 로시작하는메시지외에다른메시지들이있다. 이러한메시지들은이벤트라고불리며리소스창에서미리생성한메뉴나커맨드버튼과같은객체에서발생한메시지를의미한다. 그림2는프로그램이실행된화면 ( 왼쪽 ) 과클래스속성창의이벤트탭 ( 오른쪽 ) 을보여주고있다. 리소스창에서생성한메뉴와커맨드버튼객체가나열되어있고하위메뉴에서는해당객체에대해등록하거나편집할수있는메시지를확인할수있다. 앞서설명하였듯이클래스속성창의이벤트탭에서는리소스창에서미리생성한객체만확인할수있다. 그림2의다이얼로그창에서중앙의흰색윈도우는프로그램이실행된후에윈도우생성명령어를통해생성되는객체이다. 때문에프로그램이실행되기전시점인속성창을통해서는메시지를등록할수없고메시지를등록하려면코드를직접추가하여등록해야한다. 실제로다이얼로그기반의 MFC 프로젝트를생성하면알수없는코드로가득한소스파일들이생성되어당황하는경우가많다. 하지만응용프로그램작성자는메시지와관련된일부함수만알아도간단한테스트프로그램을작성하기에는전혀문제가없다. 문서마지막의다이얼로그기반의프로젝트를생성하는예제를통해이러한내용을확인할수있다. 3
3. MFC의구조 MFC는 C++ 클래스에대해서공부하기가장좋은예제이다. 클래스를공부하다보면생성자와소멸자, 상속, 다형성과같은내용을접하게되는데책에포함된간단한예제만가지고이러한내용들이어디에어떻게사용되는지감을잡기는어렵다. 심지어구조체대신클래스를사용하는이유조차도이해하기쉽지않다. MFC는이러한클래스의특징들을굉장히잘활용하고있다. 1) CWnd 클래스 CObject 클래스는 MFC의기본적인서비스를포함하는기반클래스이다. MFC 클래스들은 CObject 클래스를상속받는클래스와상속받지않는클래스로분류된다. 전체 MFC 클래스계층도는 http://msdn.microsoft.com/ko-kr/library/ws8s10w4.aspx에서확인할수있다. 이렇게방대한 MFC 클래스중테스트프로그램작성자가주의깊게살펴봐야하는클래스는 CWnd 클래스이다. CWnd 클래스는사용자의눈에보이는윈도우창의기본기능을제공하는클래스이다. 쉽게말해서 CWnd 객체를생성한다는것은윈도우창의생성을의미한다. 프로그램작성자는이 CWnd 클래스를상속받아서특정기능을수행하는자신만의윈도우클래스를만들수있다. MFC는컨트롤객체 ( 버튼, 콤보박스등 ) 와다이얼로그, 프레임윈도우, 뷰와같이일반적으로사용되는클래스를기본적으로제공하는데이런클래스들은모두 CWnd 클래스를상속받아서만들어진다. 그림 3. CDialogEx 클래스의상속관계 그림3은대화상자기반윈도우생성시기본클래스로사용되는 CDialogEx 클래스의상속관계를나타낸다. 대화상자도일단윈도우창의한종류이기때문에 CWnd 클래스를상속하였고대화상자의기본기능을포함하고있는 CDialog 클래스도상속하였다. CDialogEx 클래스는대화상자의배경색과배경화면을설정하는기능이추가된클래스로 MFC 라이브러리 9.0 버전이상에서사용가능하다. 이처럼 MFC는일반적으로사용되는윈도우클래스를제공하고프로그램작성자는이를상속하여자신만의윈도우클래스를만들수있다. 4
2) 윈도우인스턴스의부모-자식관계앞장에서설명하였듯이 MFC에서화면에보이는윈도우클래스 ( 버튼, 콤보박스, 뷰등 ) 들은모두 CWnd 클래스를상속받아만들어진다고하였다. 그리고실제프로그램의화면은윈도우클래스의인스턴스 ( 클래스가메모리에실제로구현된실체를의미하는말로흔히표현하는객체라고생각해도무관하다 ) 들로구성된다. 그림 4. MFC 프로그램을구성하는각윈도우인스턴스들의부모 - 자식관계도 그림 4의다이얼로그기반프로그램은총 8개의윈도우인스턴스들로구성된다. 각각다이얼로그인스턴스 1개와 CView 인스턴스 5개, CButton 인스턴스 2개이다. 이인스턴스들은그림 4의관계도 ( 오른쪽 ) 에서보이듯이부모-자식관계를가지고있다. 먼저 CView 인스턴스들중에서다른 CView 인스턴스들을포함하는가장큰 CView 인스턴스 (Parent View) 와 2개의 CButton 인스턴스들은 DialogEx 인스턴스를부모로가진다. 그리고나머지 4개의작은 CView 인스턴스 (Child View) 들은큰 CView 인스턴스 (Parent View) 를부모로가진다. 이렇게윈도우인스턴스들이부모-자식관계로구성되기때문에가지는몇가지특징들이있다. 그중가장큰특징은부모인스턴스의몇가지속성이자식인스턴스에도함께적용된다는점이다. 일반적으로자식인스턴스는부모인스턴스위에그려지게되고자식인스턴스는부모인스턴스를기준으로하는좌표계위에위치하기때문에부모인스턴스가움직이면자식인스턴스도같이움직인다. 마찬가지로부모인스턴스가삭제되면자식인스턴스도함께삭제된다. 하지만모든속성을부모인스턴스와자식인스턴스가공유하지는않는다. 예를들어윈도우바탕색이나커서의모양등은각자독립된속성값을가진다. 그림 4에서 DialogEx 인스턴스는다른인스턴스들을자식으로가지는최상위인스턴스이다. 이렇게자신스스로가하나의독립된윈도우를만드는인스턴스는 NULL 값을부모로가지게된다. 프로그램실행시생성되는기본윈도우창이아닌독립된새로운윈도우창을생성하고싶다면윈도우인스턴스생성함수 5
의인자중 CWnd* 형을가지는부모인스턴스주소값에 NULL 값을넘겨주면된다. 4. 마치며지금까지설명한내용은 MFC를이용해서윈도우즈응용프로그램을만드는데필요한굉장히기본적인부분이다. 실제로여기에서설명한내용으로는다이얼로그기반의간단한테스트프로그램을작성하는것이고작일것이다. 만약윈도우즈응용프로그램에관심이있다면 MFC를공부하기보다는 Windows API를먼저공부해야한다. 기존의윈도우즈응용프로그래머에게 MFC는굉장히편리한도구가될지모르지만윈도우즈응용프로그램을처음배우는사람에게는단지 API의방대한함수들을랩핑하고있는껍데기에불과하다. 처음 MFC를공부할때예제를따라서뭔가만들긴했는데작성한코드를실행하면왜이런윈도우가생성되는지이해하기어려운이유가이때문이다. 이문서에포함된예제도마찬가지이다. 윈도우가어떻게생성되고메시지를어떻게처리하는지등의기본적이내용이궁금하다면 Windows API에관련된책의앞부분을보길바란다. 6
예제 다이얼로그기반프로젝트에서 MyScrollView 클래스작성및테스트 목표 : 이미지디스플레이와스크롤바기능을지원하는 MyScrollView 클래스작성. 핵심 : MFC에서제공하는 CScrollView 클래스는스크롤바와관련된기능들을기본적으로포함하고있다. CScrollView 클래스를상속받으면이런기능들의대부분을그대로사용할수있다. 환경 : Microsoft Visual Studio Professional 2012 과정 : 1. 다이얼로그프로젝트생성 - 템플릿 : MFC MFC 응용프로그램프로젝트이름 : TestViewer - 응용프로그램종류 : 대화상자기반 7
- 기본텍스트컨트롤삭제 안내를위한일종의주석같은의미로삽입되어있는텍스트컨트롤이므로삭제한다. 2. MyScrollView 클래스작성 - 클래스추가 : MFC MFC 클래스 8
클래스이름 : MyScrollView 기본클래스 : CScrollView - TestViewerDlg.h 에 MyScrollView.h 포함 최상위인스턴스인다이얼로그창에 MyScrollView 인스턴스를생성하기위해헤더파일을포함한다. - MyScrollView 클래스의인스턴스생성함수선언및구현 MyScrollView.h 파일의 MyScrollView 클래스선언아래쪽에인스턴스생성함수를선언한다. 9
MyScrollView.cpp 파일에인스턴스생성함수를구현한다. MFC에서제공하는기본클래스들중에서몇몇클래스는기본생성자를통해객체를생성할수없다. CScrollView의상위클래스인 CView가이에해당한다. ( 보통생성자가 protected 형태로선언되어외부에서접근할수없게되어있다. Frame이나 Document 클래스의유도클래스도이에해당한다.) 이런경우일반적인방법으로객체를생성할수없고런타임클래스에해당클래스를등록후객체를생성해야한다. MSDN에런타임클래스에대한자세한내용이설명되어있다. 3. 중간테스트 - CTestViewerDlg 클래스에 WM_CREATE 메시지추가 속성창의메시지등록기능을통해메시지를등록하면함수및메시지맵에메시지를등록하는코드 가자동으로삽입된다. 10
- MyScrollView 포인터변수선언엑세스 : private 변수형식 : MyScrollView* 변수이름 : _view1 클래스뷰 CTestViewerDlg 클래스오른쪽클릭 추가 변수추가 코드상에서직접변수를추가하지않고마법사를사용할경우클래스생성자에변수를초기화하는 코드가자동으로추가된다. - MyScrollView 인스턴스생성 TestViewerDlg.cpp 파일의 CTestViewerDlg::OnCreate() 함수의구현부에인스턴스생성함수를호출 하는코드를추가한다. 최상위인스턴스인다이얼로그창위에 MyScrollView 인스턴스를생성하기위해부모인스턴스의주소값인자는 this를넘겨준다. (AfxGetMainWnd() 함수를사용해도된다.) 생성되는윈도우의크기는적절하게조절한다. 인스턴스의 ID를의미하는 nid 인자는리소스창에서생성한컨트롤객체의 ID와겹치지않게적당한 값을사용한다. (Resource.h 파일참조 ) 11
- 빌드및실행 MyScrollView 인스턴스의창크기에비해가로및세로스크롤바의크기가작기때문에아직화면에 스크롤바가표시되지않는다. 4. MyScrollView 에이미지그리기 - GDI+ 라이브러리추가 stdafx.h 파일에선언하면프로젝트의다른파일에서도 GDI+ 라이브러리를사용할수있다. GDI는윈도우즈에서제공하는그래픽출력라이브러리로화면에선을긋거나이미지를출력하는등의그래픽작업을지원한다. GDI+ 는윈도우즈 XP 이상에서제공하는기존의 GDI보다그래픽및객체화에대한부분이개선된라이브러리이다. 12
- GDI+ 라이브러리초기화 TestViewer.cpp 에서작업한다. GDI+ 라이브러리를사용하기위해서는반드시초기화단계가필요하다. - ExitInstance() 함수재정의 다이얼로그기반프로젝트는프로젝트생성시 ExitInstance() 함수의재정의가포함되어있지않기때 문에직접추가해준다. 13
- GDI+ 라이브러리해제 재정의한 ExitInstance() 함수에 GDI+ 라이브러리를해제하는구분을추가한다. GDI+ 초기화와마찬 가지로꼭필요한단계이다. 여기까지 GDI+ 라이브러리를사용하기위한준비단계이다. - 이미지렌더링 이미지파일로부터데이터를읽어오고화면에그려준다. SetScrollSizes() 함수를통해스크롤바의크기를이미지의크기와같아지게설정한다. 4. 최종테스트 - 빌드및실행 MyScrollView 인스턴스창에이미지가렌더링되고스크롤바를이용해이미지전체를살펴볼수있 다. 직접 CWnd 클래스를상속받아서스크롤바기능을지원하는윈도우클래스를만들기보다는 MFC 에서제공하는기본클래스를상속받아서만드는것이훨씬효율적이다. 14
- MyScrollView 인스턴스추가 My::CreateMyScrollView() 인스턴스생성함수를한번더호출하여인스턴스창을하나더추가할수있다. MyScrollView 클래스를작성했던앞의과정을이해하고있다면클래스를조금수정하여위의그림처럼각각의창이다른이미지를렌더링하도록만들수있을것이다. 지금까지작성한 MyScrollView 클래스는아직보완할점이많다. 예를들면마우스휠을돌려서스크롤하는기능등은작동하지않는다. 이런 MFC의기능과관련된부분은 MFC 관련서적을조금만공부하면충분히작성할수있을것이다. References [1] 최호성, 열혈강의 Visual C++ 2008 MFC 윈도우프로그래밍, FREELEC, 2009. [2] 김상형, Windows API정복, 가남사, 2001. [3] MFC 프로그래밍연구회, Visual C++ 2010 MFC 프로그래밍, 세진북스, 2011. 15