Docstoc

Visual C++ 실전프로그래밍 Part01 - 윈도우 프로그래밍 기본

Document Sample
Visual C++ 실전프로그래밍 Part01 - 윈도우 프로그래밍 기본 Powered By Docstoc
					저자/이재원

이재원씨는 1995년 현대전자를 거쳐 1996년부터 삼성SDS에서 근무하면서
윈도우와 UNIX 환경의 애플리케이션 개발 업무를 담당하고 있다. 온라인
교육 사이트인 e-Campus(http://www.e-campus.co.kr)에서 Visual C++
관련 강좌의 개발 및 과정 튜터로 활동하고 있고, 삼성멀티캠퍼스에서 웹사
이트 구축방법론 과정을 강의했다. 또한 전문가 사이트인 Xpert에서 C/C++
언어(http://cpp.xpert.co.kr)의 전문가로도 활동하고 있다.

  - E-mail: netisky@korea.com
  - HomePage: http://www.sds.pe.kr



펴낸이: 백종수
펴낸곳: 디지털디자인(http://www.ddcom.co.kr)
주소: 서울시 강남구 역삼동 707-1 두꺼비빌딩 1102
전화: 02-538-0183
팩스: 02-538-0184
e-Mail: webmaster@ddcom.co.kr



기획/편집 책임: 박승현(gabriel@ddcom.co.kr)
초판 발행: 2002년 12월 30일
정가: 5,000원



문서의 무단 복제 및 전제를 금합니다.
Published by Digital Design
                                                       3



머리말

2002년 여름 월드컵의 감동이 아직도 귀에 들리는 듯한데 또 다시 한해가 저물어가고 있
다. 매년 새해가 되면 여러 가지 소망과 새로운 다짐을 가슴속에 담게 되지만, 시간이 지
날수록 허전함을 느끼게 되고 결국은 아쉬움으로 귀결되는 것 같다.

프로그래밍 언어를 어느 정도 배우고 익혔다고 하더라도 막상 쓸만한 애플리케이션을 개
발하기 위해 컴퓨터 앞에 앉으면 무엇부터 해야 할지 막막한 경우가 많다. 여러 가지 원인
이 있겠지만 가장 큰 이유는 프로그래밍 언어를 배울 때 교과서적인 내용을 위주로 학습하
기 때문이 아닌가 생각한다. 물론 프로그래밍 언어의 문법적인 내용도 중요하다. if 문은
어떤 경우에 사용하고, 순환문을 구현하기 위해 while loop를 사용한다는 것을 배우는 것
은 프로그래밍의 기본이 되는 중요한 일이라고 할 수 있다. 하지만 실제 업무에 사용되는
애플리케이션을 만들려면 교과서 같은 내용보다는 다양한 분야의 애플리케이션에 적용할
수 있는 예제 코드와 테크닉이 필요하다고 할 것이다.

이 책은 이러한 독자의 요구를 충족시킬 수 있도록 Visual C++를 이용하여 애플리케이션
을 제작할 때 유용하게 사용되는 기술과 이를 활용하는 방법, 실무 프로젝트에 사용되는
현실적이고 구체적인 예제 코드를 소개하고 있다. 또한 필자가 강사로 활동하고 있는 온라
인 교육과정에서 물어보는 질문들을 통해 학습자가 어려워하는 부분이 무엇인지, 그리고
좀더 자세한 설명이 필요한 부분은 무엇인지 파악하여 이를 반영하고자 노력했다. 물론 이
책 한 권으로 필요한 모든 것을 얻을 수는 없을 것이다. 하지만 시스템 개발을 위해 인터
넷과 무수한 참고자료를 찾아가며 밤을 지새울 독자에게 작은 도움이라도 줄 수 있으면 더
이상 바랄 것이 없겠다.

끝으로 이 책이 나오기까지 필자와 함께 부족한 부분을 채워주고 많은 도움을 준 디지털디
자인의 박승현 과장님과 백종수 사장님께 감사의 말을 전한다. 아울러 집필 기간 동안 함
께 하는 시간이 적어 항상 미안했던 사랑하는 나의 아내와 아들 승준이에게 감사의 말을
전하며 이 책을 바친다.




                                      2002년 12월 이재원
4


    목차
        머리말 / 3

        목차 / 4

        이 책의 구성에 대해 / 8

        용어에 대해 / 10

        준비 사항 / 11




    ▣   1장 Visual C++ 개요
        애플리케이션 개발 환경 / 13
            윈도우 운영체제 이전의 개발 툴 / 13
            윈도우 운영체제 이후의 개발 툴 / 16
            잘못된 오해 / 17

        Visual C++의 개요 / 18
            Visual C++의 역사 / 18
            Visual C++ .NET / 19
            Visual C++ .NET의 설치 / 23



    ▣   2장 Visual C++의 주요 구성
        Visual C++의 메뉴 구성 / 24
            [File] 메뉴 / 24
            [Edit] 메뉴 / 25
            [View] 메뉴 / 26
            [Project] 메뉴 / 26
            [Build] 메뉴 / 27
            [Debug] 메뉴 / 27
            [Tools] 메뉴 / 28
            [Window] 메뉴 / 28

        Explorer, View, Window / 28
            Solution Explorer / 28
            Class View / 30
            Server Explorer / 30
            Resource View / 32
                                                    5


         Properties Window     / 33
         Output Window / 34

    에디터 / 35
         List Members / 35
         Parameter Info / 36
         Quick Info / 36
         Complete Word / 37
         Outlining / 37
         Navigation Bar / 38



▣   3장 Application Wizard
    Application Wizard / 40
         개요 / 40
         Application Wizard를 이용한 프로젝트 템플릿 생성 / 41

    Class Wizard / 52



▣   4장 Win32 API 애플리케이션의 개요
    윈도우 프로그래밍 모델 / 54

    Windows API / 55

    Windows API 카테고리 / 59



▣   5장 첫 번째 Win32 API 애플리케이션
    HelloWorld 애플리케이션 / 60
         프로젝트 생성 / 60
         소스 코드 수정 / 64
         빌드와 실행 / 65

    Win32 API 애플리케이션의 구조 / 66
         WinMain() 함수 / 67
         MyRegisterClass() 함수 / 72
         InitInstance() 함수 / 74
         메시지 루프 / 76
         윈도우 프로시저 / 77
6


    ▣   6장 MFC 애플리케이션 개요
        MFC의 개요 / 81

        MFC 라이브러리의 구성 / 82
            MFC 계층도 / 83
            MFC 클래스로 포장된 API 함수 / 83
            기타 MFC 클래스 / 84
            전역함수 / 85

        MFC Framework / 85



    ▣   7장 첫 번째 MFC 애플리케이션
        HelloMFC 애플리케이션 / 88
            프로젝트 생성 / 88
            소스 코드 수정 / 89
            빌드와 실행 / 91

        MFC 애플리케이션의 구조 / 91
            실행 구조 / 92
            메시지 처리 / 96
            MFC 애플리케이션의 실행 원리 / 97



    ▣   8장 Document/View 아키텍처
        Document/View 아키텍처의 구조 / 115

        CDocument::UpdateAllViews() 멤버함수 / 116

        CView::GetDocument() 멤버함수 / 117
            프로젝트 생성 / 119
            소스 코드 작성 / 119
            빌드와 실행 / 124



    ▣   9장 간단한 출력 예제
        Device Context / 127

        CDC 클래스 / 127
            TextOut() 함수 / 130
            소스코드 수정 / 131
            빌드와 실행 / 132
                                                     7



▣   10장 콘솔 애플리케이션
    콘솔 애플리케이션 / 134
        프로젝트 생성 / 135
        소스코드 작성 / 136
        빌드와 실행 / 140
        콘솔 애플리케이션에서 메시지 박스 표시하기 / 140



▣   11장 디버깅
    오류의 종류 / 142
        문법적 오류 / 142
        논리적 오류 / 143

    Visual C++의 디버깅 기능 / 144
        Step Into / 144
        Step Over / 144
        Step Out / 145
        Breakpoint / 145
        Watch 윈도우 / 145
        Autos, Locals, Memory, Registers 윈도우 / 146
        Edit and Continue / 147
        프로젝트 생성 / 148
        소스코드 작성 / 148
        디버깅 / 150
                                                             9


5부에서는 그래픽 처리에 대해 학습한다. 윈도우 운영체제에서 그래픽을 처리하는 기본구
조인 Device Context를 중심으로, 각종 도형을 그리는 방법, 문자 출력 방법, 이미지 처
리 방법 등에 대해 학습한다.

6부에서는 데이터베이스 프로그래밍에 대해 학습한다. 간단한 데이터베이스 애플리케이션
에 많이 사용되는 MDB와 기업용으로 널리 사용되는 Microsoft사의 SQL Server를 이용
한 애플리케이션을 만들어 본다. 또한 실제 업무에서 가장 많이 사용되는 문서인 엑셀 파
일을 다루는 방법도 함께 학습한다.

7부에서는 네트워크와 인터넷 프로그래밍에 대해 학습한다. 서버인 UNIX 또는 Windows
2000 Server 운영체제에서 실행되는 애플리케이션과 데이터를 주고 받는 방법을 학습하
며, ASP(Active Server Pages)와 같은 서버 스크립트에서 사용 가능한 컴포넌트를 구현하
는 방법에 대해 알아보고, HTTP나 FTP 프로토콜을 이용해서 웹 페이지를 조회하거나 파
일을 업로드/다운로드하는 기능을 구현해 본다.




이 책의 진행 방식을 보면, 먼저 학습하고자 하는 주제의 개요와 기본적인 개념을 설명한
후 예제 프로그램을 작성하게 된다. 이 때 예제는 단순한 소스 코드의 나열에 그치는 것이
아니라 기능별로 자세한 설명이 제시되며, 경우에 따라서는 개요나 기본 개념 설명보다 소
스 코드의 설명이 더 많을 때도 있다.

이 책의 본문에서 설명하고 있는 소스 코드는 모두 별도의 프로젝트 파일(Visual C++
.NET 및 6.0 버전)로 제공된다. 따라서 책 본문에는 소스 코드 전체를 기술하지 않으며, 코
드 중 설명에 필요한 부분만 선별적으로 기술하였다. Visual C++는 프로젝트를 생성할
때 기본적인 코드를 자동으로 생성해 주기 때문에 이런 기본 코드를 본문에 기술하는 것은
책만 두껍게 만드는 것으로 그다지 효과적인 일은 아니다. 따라서 애플리케이션의 전체 소
스를 봐야 할 경우에는 별도로 제공되는 프로젝트 파일을 이용해주기 바란다.
10


     용어에 대해

     IT(Information Technology)에 관련된 책을 집필할 때 가장 신경쓰이는 부분 중 하나가 바
     로 용어 선택의 문제이다. 정보기술 분야에서 사용되고 있는 용어의 대부분이 영어이다 보
     니 번역을 전혀 하지 않으면 너무 딱딱한 문장이 되고, 반대로 너무 번역에 충실하면 원문
     이 가지는 느낌을 그대로 전달하기 어려운 문제가 있다. 따라서 이 책에서는 가급적 개발
     현장에서 사용되는 용어를 우선적으로 사용하는 원칙을 세웠으며, 일반적으로 사용되는
     용어는 한글로 번역하여 기술하였고, 특별히 원문이 필요한 경우에는 함께 병기하여 최대
     한 이해를 돕도록 했다.

     이와 함께 Visual C++ 자체에서 사용되는 용어는 영어 원문을 사용하는 것이 일반적이므
     로 한글 발음으로 표기하거나 번역해서 표기하지 않고 원문을 그대로 사용했다. 예를 들
     어, Application Wizard는 ‘애플리케이션 위저드’나 ‘응용프로그램 마법사’라고 풀어쓰지
     않고, Application Wizard라는 용어를 그대로 사용했다.
                                                                           11



준비 사항

이 책을 읽기 위해서는 C++ 언어에 대한 기본적인 내용을 이해하고 있어야 한다. Visual
C++는 C++ 언어를 기반으로 하는 윈도우 애플리케이션 개발 도구이기 때문에 이에 대한
이해가 없이는 책의 내용을 이해하기 어렵다. 물론 Windows API만 사용해서 애플리케이
션을 개발한다면 C++ 언어나 객체지향에 대한 내용은 그리 중요하지 않을 수도 있다. 하
지만 Visual C++가 제공하는 다양한 기능을 충분히 활용하려면 MFC의 사용은 필수적이
며 이를 위해서 C++ 언어와 객체지향에 대한 기본적인 내용을 이해하고 있어야 한다.




이 책은 Visual C++에 대한 내용을 다루고 있는 책이다. 따라서 Visual C++가 설치되어
있어야 하는데, 집필하고 있는 현재(2002년 10월) Visual C++의 최신 버전은 Visual C++
.NET이다. 따라서 이 책의 학습 내용과 예제는 Visual C++ .NET을 기준으로 하고 있다.
하지만 Visual C++ .NET의 이전 버전인 Visual C++ 6.0도 나름대로의 특징이 있고, 아
직도 많이 사용되고 있기 때문에 Visual C++ 6.0에 대한 내용을 함께 다루고 있다. 따라
서 두 버전 간에 차이가 없으면 Visual C++ .NET을 기준으로 설명하되, 동일한 내용이지
만 두 버전 간에 차이가 있으면 Visual C++ .NET을 기준으로 먼저 설명하고 6.0에 대한
내용을 보완하는 형태로 구성되어 있다. 따라서 어느 버전의 Visual C++를 사용해도 이
책의 학습에는 지장이 없다. 다만 Visual C++ 6.0을 사용하는 경우에는 보다 확장된 기
능의 구현을 위해 Platform SDK를 설치하는 것이 좋다.

또 이 책의 후반부, 즉 7부로 가면 DBMS에 대한 내용과 네트워크와 인터넷에 대한 내용
을 학습하게 되는데, 이 때는 DBMS가 설치된 서버 운영체제나 UNIX 운영체제에 접근할
수 있는 환경이 필요하다. 물론 이러한 환경이 갖추어지지 않아도 이론적인 부분을 학습하
거나 책에 나온 소스를 이해하는 데 문제는 없다. 다만 실습이 어려울 수 있기 때문에 서
버 운영체제 환경을 요구하는 것이다. 이 책에서 사용된 예제는 다음과 같은 환경에서 작
성하고 테스트했다.


  ∙ Client OS: Windows XP Professional, Windows 2000 Professional

  ∙ Server OS: Windows 2000 Server, Linux(Mandrake 8.1)

  ∙ Visual C++ 6.0(Platform SDK 설치), Visual C++ .NET(Visual Studio .NET)

  ∙ DBMS: Microsoft SQL Server 2000
12

     이 책에서 제공되는 소스코드는 별도로 제공되는 예제 파일을 이용하면 바로 실행해 볼 수
     있다. 다만 Visual C++ 6.0과 Visual C++ .NET은 프로젝트 파일을 관리하는 방식에 약
     간의 차이가 있기 때문에(Visual C++ 6.0은 프로젝트를 구성하고 있는 파일을 Workspace라는
     단위로 관리하는데, Visual C++ .NET은 Solution이라는 단위로 관리한다) 제공되는 예제도 두
     버전의 예제 파일을 동시에 제공하고 있다. 물론 Visual C++ .NET은 하위 호환성을 가지
     고 있기 때문에 Visual C++ 6.0 프로젝트 파일을 그대로 실행할 수는 있지만, 컨버전 작
     업을 거쳐야 하고 일부 확장된 기능은 소스를 수정해야 할 필요가 있다. 이런 이유로 두
     버전의 예제를 별도로 제공하고 있으니 사용하는 컴파일러의 버전에 맞는 예제 파일을 이
     용하면 된다.

     운영체제의 선택도 중요한 부분이다. 일반적으로 Windows 98 이상의 운영체제를 사용한
     다면 이 책의 예제를 무리없이 수행할 수 있다. 하지만 과정 후반으로 가면 인터넷 관련
     부분을 학습하게 되고 웹 서버(Web Server)와 연동한 프로그램을 개발하게 되므로, 가급적
     이면 Windows 2000 Professional 이상의 운영체제를 사용할 것을 권장한다.
                                                                                   13
             Visual C++ 개요
             윈도우 운영체제에서 실행되는 애플리케이션을 제작하기 위한 개발 툴은 여러 가지가 있다. 또한
             각각의 툴은 개발하고자 하는 프로그램의 유형이나 개발 방법에 따라 고유한 특징을 가지고 있
             다. 1장에서는 이 책에서 배우게 되는 Visual C++에 대한 전반적인 내용과 개발 툴의 발전 과정
Chapter 01   등을 알아본다.




              애플리케이션 개발 환경
              먼저 애플리케이션 개발 툴이 어떻게 발전해 왔는지 알아봄으로써 독자의 이해를 돕고자
              한다.




              윈도우 운영체제 이전의 개발 툴
              예전의 MS-DOS(Microsoft Disk Operating System: 윈도우 이전에 사용되던 텍스트 환경의 운
              영체제)나 초기의 윈도우 운영체제 시절, 개발 툴이라고 하면 애플리케이션을 개발하기 위
              한 툴이라는 개념보다는 프로그래밍 언어로서의 컴파일러나 링커를 뜻하는 경우가 많았
              다. 이 때만 해도 컴퓨터의 속도나 메모리가 충분하지 않았기 때문에 지금의 개발자들이
              당연하게 요구하는 여러 가지 기능을 구현하기가 매우 어려웠다.

              따라서 그 당시에는 지금은 사라진 5.25인치 디스켓에 컴파일러를 넣어서 갖고 다니면서
              소스 파일을 작성하고 컴파일하는 것이 프로그램을 만드는 일반적인 방법이었다. 이 때 주
              로 사용하던 컴퓨터가 바로 IBM사에서 출시된 XT라는 컴퓨터였는데, 메인 메모리로
              640KB를 사용하고 하드 디스크없이 플로피 디스크 드라이브만 1개 또는 2개 달린 모델이
              었다(더 이전, 즉 1980년대 초반에 사용하던 컴퓨터인 Apple II나 패미콤, 퍼스컴도 있지만, 잘 이
              해되지 않는 독자를 위해 이야기하지 않겠다). 하지만 지금은 필자도 펜티엄 4 CPU에 메인 메
              모리가 640MB인 컴퓨터를 사용하고 있다(많은 세월이 흘렀음을 느끼게 된다).

              80년대 중반의 프로그래밍 환경을 살펴보면, 주로 사용된 언어는 C, FORTRAN, COBOL
              등이었고, 자료를 관리하는 프로그램을 만들 때는 dBASE III Plus나 클리퍼(Clipper) 같은
              프로그램을 주로 이용했다. FORTRAN이나 COBOL 등은 주로 대형 컴퓨터와 연결된 터미
              널을 이용해서 작업했기 때문에 PC 용 개발 툴로는 그다지 유명한 제품이 없었지만, C 프
              로그램을 만들기 위한 컴파일러로는 MS C/C++(Microsoft C/C++)와 그 유명한 Turbo
              C/C++가 있었다(사실 Turbo C++는 Turbo C와는 별개의 제품으로, Turbo C 이후에 나온 제품
              이다).
14

                Turbo C는 프로그램의 개발과정을 획기적으로 개선한 통합개발환경(Integrated Develo-
                pment Environment, IDE)을 채용했기 때문에 기존의 방식에 비해 매우 간편하게 프로그
                램을 개발할 수 있었다. Turbo C 이전에는 에디터를 이용해서 소스를 작성하고, 다시 이
                파일을 컴파일하고 링크하는 과정을 별도로 수행했었다(UNIX에서는 지금도 이런 방법을 사
                용하고 있긴 하다).

                [그림 1-1]은 Visual C++ 6.0을 이용해서 예전에 프로그램을 개발하던 방식을 그대로 재
                현해 본 것이다. 별도의 에디터를 사용하지는 않았으며, copy 명령어를 이용하여 간단한
                C 소스 파일을 작성한 후 커맨드 상에서 컴파일한 다음 실행한 결과를 나타내었다.


       그림 1-1
     예전의 프로그램
        개발 화면




                이 화면은 윈도우 운영체제에서 명령 프롬프트를 통해 작업을 수행한 것이지만, 예전에는
                MS-DOS 환경에서 수행되었다. 화면을 통해 알 수 있겠지만, 프로그램을 작성하는 과정
                을 보면, 일단 별도의 에디터를 이용해서 소스 파일을 작성한 다음, 컴파일러를 실행하는
                과정을 거치는 것을 볼 수 있다. 이와 같이 소스 파일의 작성, 컴파일, 실행 과정이 별도로
                이루어지기 때문에 프로그램을 작성하는 일은 어느 정도의 노동력을 필요로 하는 작업이
                었다.

                그러던 것이 Turbo C처럼 통합개발환경을 제공하는 컴파일러의 출현으로 인해 프로그래
                밍 작업이 획기적으로 간편해졌다. 우선 통합개발환경을 실행한 후, 내장되어 있는 편집기
                로 소스 파일을 작성한 다음, 컴파일, 링크 메뉴를 선택하기만 하면 실행파일이 만들어지
                고, 오류가 발생하더라도 IDE에 내장되어 있는 디버거로 쉽게 수정할 수 있게 되었다.
                Turbo C는 나중에 C++를 사용할 수 있는 Turbo C++로 발전하게 되고, 이후에는
                Borland C++라는 새로운 제품으로 발전되었다.
                                                                              15


                   [그림 1-2]는 Turbo C++를 커맨드 모드에서 실행한 모습이다. 커맨드 모드에서 실행할
                   때 지정할 수 있는 여러 가지 옵션이 도움말로 나타나는 것을 볼 수 있다.


       그림 1-2
Turbo C++ 실행 화면




                   다음의 [그림 1-3]은 Turbo C++의 IDE이다. 이 그림은 실제 IDE의 실행 화면이 아니라
                   Turbo C++를 설치했을 때 기본으로 설치되는 가이드 프로그램을 실행한 화면이다. 비록
                   커맨드 모드에서 실행되고 있긴 하지만, 편집기를 내장하고 있으며, 메뉴 방식을 통해 컴
                   파일, 실행, 디버깅 등의 작업이 수행 가능한 것을 볼 수 있다. 또한 하나의 파일만 편집할
                   수 있는 것이 아니라 여러 개의 파일을 동시에 편집할 수도 있는 등 윈도우 애플리케이션
                   과 비슷한 구조를 가지고 있음을 알 수 있다.


       그림 1-3
  Turbo C++의 IDE
16


                  윈도우 운영체제 이후의 개발 툴
                  윈도우 운영체제가 등장하면서 프로그램에는 많은 변화가 생겼다. 기존의 DOS 프로그램
                  은 단순한 텍스트 기반의 프로그램이기 때문에 사용자 인터페이스를 포함한 입출력 요소
                  가 비교적 간단했지만, 윈도우에서 실행되는 프로그램에는 메뉴, 툴바, 아이콘, 사운드 등
                  여러 가지 시각적 요소(리소스)가 포함되었다. 따라서 이 때부터 윈도우 프로그램을 개발
                  하기 위한 도구에 Visual이라는 용어가 사용되기 시작했다.

                  [그림 1-4]는 윈도우 프로그램에 사용되는 대표적인 리소스들을 보여주고 있다.


         그림 1-4
     윈도우 프로그램에서
       사용되는 리소스




                  윈도우 운영체제의 등장은 개발 툴에도 많은 변화를 가져왔다. DOS 시절에는 소스 파일
                  과 헤더 파일만 있으면 어지간한 프로그램을 만드는 데는 문제가 없었다. 물론 라이브러리
                  를 사용하는 경우도 있지만, 라이브러리도 결국 소스 파일을 컴파일해서 만드는 것으로 보
                  면 같은 과정을 거친다고 할 수 있다. 따라서 Turbo C++와 같은 IDE 환경에서 개발하든
                  MS C/C++로 개발하든 프로그램을 만드는 단위는 소스 파일이었고, 이들을 묶어서 관리
                  하기 위한 별도의 체제는 사용되지 않았다.

                  하지만 윈도우에서 실행되는 애플리케이션(이 때부터 프로그램이란 용어 대신 애플리케이션이
                  라는 용어를 사용하게 된다)을 개발하기 위해서는 단순한 소스 파일만으로는 부족하다. 윈도
                  우 애플리케이션에서는 DOS 프로그램과 다르게 메뉴, 툴바 등을 구현하기 위한 여러 가
                  지 리소스(그림, 사운드 등)가 포함되기 때문에 하나의 애플리케이션을 만들기 위해서 많은
                  파일이 필요하게 되었다.
                                                                      17


             따라서 리소스를 체계적으로 관리하기 위한 방법으로 사용된 것이 ‘프로젝트’라는 개념이
             다. 즉, 하나의 애플리케이션을 만들기 위해 필요한 소스 파일, 헤더 파일, 그리고 여러 가
             지 리소스 파일을 하나의 그룹(프로젝트)으로 묶어줌으로써 구성 요소를 보다 편리하게 관
             리할 수 있게 되었고, 이러한 과정을 거치면서 새로운 기능이 보강되고 사용자 인터페이스
             가 점차 편리해지면서 현재 사용하고 있는 개발 툴의 모습으로 변모해간 것이다.

             [그림 1-5]는 애플리케이션의 구성 요소를 관리하기 위한 프로젝트 화면이다.


    그림 1-5
  애플리케이션의
구성요소를 관리하는
   프로젝트 화면




             화면을 보면 하나의 애플리케이션을 만들기 위해 필요한 여러 가지 요소가 일목요연하게
             정리되어 있으며, 위에서부터 소스 파일, 헤더 파일, 아이콘 파일, 그리고 툴바를 표현하
             기 위한 비트맵 파일 등이 하나의 프로젝트 이름으로 통합되어 있는 것을 볼 수 있다.

             지금까지 애플리케이션 개발 환경에 관련된 내용을 굳이 DOS 시절까지 거슬러 올라가 설
             명한 이유는 Visual 개발 툴로 프로그래밍을 처음 시작하는 독자에게 현재 편리하게 사용
             하고 있는 개발 툴이 단지 하늘에서 떨어진 것이 아니라 많은 세월을 거쳐 발전되어 온 것
             임을 알아주었으면 하는 바람에서이다.




             잘못된 오해
             필자는 프로그래밍 언어로 Visual C++를 배운다는 식의 말을 종종 듣곤 한다. 보통 C나
             C++ 언어를 먼저 배우지 않고, Visual C++를 바로 학습할 때 이처럼 생각하는 경우가 많
             다. 하지만 이것은 적절한 표현이라고 할 수 없으며, C/C++ 언어를 기반으로 하는 윈도우
18

                       애플리케이션 개발 툴인 Visual C++를 배운다는 표현이 적합하다. C나 C++ 언어는
                       Visual C++ 이전에도 존재하던 프로그래밍 언어이며, 가장 널리 사용되는 프로그래밍 언
                       어이다. 또한 C/C++는 윈도우 환경뿐 아니라 UNIX나 Linux에서도 사용할 수 있다. 따라
                       서 Visual C++는 윈도우 애플리케이션을 개발하기 위해 필요한 기능(메뉴 작성, 화면 레이
                       아웃의 설계 등)을 Visual한 형태로 제공하고, 세부 기능의 구현은 C/C++ 언어를 이용해서
                       기술되는 개발 툴로 생각하는 것이 옳다.

                       대부분의 윈도우 애플리케이션 개발 툴은 이처럼 기반이 되는 언어가 존재한다. 예를 들어
                       델파이의 기반 언어는 Pascal이고, Visual Basic의 기반 언어는 Basic이다. 또한 Power
                       Builder처럼 자체 스크립트를 사용하는 개발 툴도 있다. 이런 개발 툴들은 이름과 모양은
                       조금씩 다르지만 기본적으로 Visual한 방법으로 애플리케이션의 형태를 디자인하고, 구성
                       요소에 대한 세부 처리는 기반 언어로 기술한다는 공통점이 있다.




                       Visual C++의 개요
                       Visual C++가 어떻게 발전해 왔는지 알아보고, 이 책을 학습하기 위해 알아두어야 할 기
                       본 지식을 설명한다.



                       Visual C++의 역사
                       Visual C++는 C/C++ 언어를 기반으로, Win32 API 애플리케이션이나 MFC 애플리케이
                       션 같은 윈도우 애플리케이션을 개발할 수 있는 툴이다. Visual C++는 Microsoft사의 제
                       품으로, MS C/C++로부터 발전되어 왔으며, 2002년 현재 최신 버전은 Visual C++ .NET
                       이다. [표 1-1]은 Visual C++의 변천사를 정리한 것이다.


            표 1-1      출시일               버전
     Visual C++의 변천사
                       1992. 3           MS C/C++(MFC 1.0)
                       1993. 2           Visual C++ 1.0(MFC 2.0)
                       1993.12           Visual C++ 1.5(MFC 2.5)
                       1994. 9           Visual C++ 2.x(MFC 3.x)
                       1995.12           Visual C++ 4.0(MFC 4.0), Visual C++ 4.2
                       1997. 3           Visual C++ 5.0(MFC 4.21)
                       1998. 9           Visual C++ 6.0(MFC 6.0)
                       2002. 3           Visual C++ .NET(MFC 7.0)
                                                                                            19


                    [표 1-1]의 내용 중 MS C/C++는 MS-DOS 환경에서 사용할 수 있는 텍스트 기반의
                    C/C++ 컴파일러이며, MFC(Microsoft Foundation Class Library)는 윈도우 애플리케이션
                    개발에 필요한 기능을 클래스로 구현한 라이브러리이다. 버전이 증가하는 형태를 보면
                    Visual C++ 2.x에서 3.0을 건너뛰고 바로 4.0이 된 것을 볼 수 있는데, 이는 MFC 버전
                    과 보조를 맞추기 위한 것이다. 이전 버전의 Visual C++에 대한 자세한 정보를 얻고 싶으
                    면 다음의 주소로 접속해서 Microsoft 홈페이지를 방문하기 바란다(URL은 변경될 수 있다).


                     ∙ http://msdn.microsoft.com/visualc/productinfo/previous/default.asp

                     ∙ http://www.sds.pe.kr/redirectURL?index=1




                    Visual C++ .NET
                    Visual C++ .NET은 Visual C++ 6.0의 후속 버전이긴 하지만, 단순한 후속 버전 이상의
                    의미를 가지고 있다. Visual C++ 6.0이 주로 윈도우 애플리케이션 개발에 중점을 두고
                    있는 반면, Visual C++ .NET은 윈도우 애플리케이션뿐 아니라 .NET 애플리케이션, 웹
                    애플리케이션, XML 웹 서비스까지 개발할 수 있는 등 더 향상된 기능을 제공하고 있다.


      그림 1-6
Visual Basic 6.0과
   Visual C++ 6.0
         실행 화면
20

                           하지만 개발 환경 측면에서 볼 때 Visual C++ 6.0과 Visual C++ .NET의 가장 큰 변화는
                           통합개발환경에 있다고 볼 수 있다. Visual C++ 6.0은 보통 Visual Studio 6.0의 한 부
                           분으로, Visual Studio 6.0을 설치하면 Visual Basic 6.0, Visual InterDev 6.0, Visual
                           FoxPro 6.0 등의 개발 툴과 함께 설치된다. 또한 이 개발 툴들은 각각 고유한 개발 환경
                           을 갖고 있기 때문에 Visual Basic 애플리케이션을 개발하려면 Visual Basic 6.0을 실행
                           해야 하고, Visual C++ 애플리케이션을 개발하려면 Visual C++ 6.0을 별도로 실행해야
                           하며, 개발 툴 사이에는 호환성이 존재하지 않는다. [그림 1-6]은 Visual Basic 6.0과
                           Visual C++ 6.0을 동시에 실행하고 있는 화면이다.

                           이에 비해 Visual C++ .NET이나 Visual Basic .NET(Visual Basic 6.0의 후속 버전)은
                           Visual Studio .NET이라는 개발 환경으로 통합되어 있기 때문에 애플리케이션별로 다른
                           개발 툴을 실행하는 것이 아니라 Visual Studio .NET이라는 단일화된 통합개발환경을 실
                           행한 후, 개발할 프로젝트 유형을 선택하는 방법으로 시작하게 된다. [그림 1-7]은 Visual
                           Studio .NET에서 개발할 프로젝트 유형을 선택하는 화면이다.


              그림 1-7
     Visual Studio .NET의
            프로젝트 선택




                           [그림 1-7]을 보면 왼쪽에 있는 Project Types에서 Visual Basic Projects, Visual C#
                           Projects, Visual C++ Projects 등 개발할 프로젝트의 종류를 선택하게 되어 있는 것을
                           볼 수 있다. 이처럼 Visual Studio .NET에서 프로젝트를 생성하려면 먼저 개발할 프로젝
                           트의 유형을 지정해줘야 한다. 프로젝트를 생성하기 위한 자세한 방법과 절차는 이후 과정
                           에서 다룰 것이다.

                           개발 환경이 통합되었다는 것은 또 다른 장점을 제공한다. Visual Studio 6.0을 사용할
                           때는 Visual C++나 Visual Basic이 별도로 존재하기 때문에 하나의 개발 툴에서 다른 개
                           발 툴의 프로젝트를 함께 관리할 수 없다. 예를 들어 Visual C++ 프로젝트를 개발하면서
                           동일한 개발 환경 내에서 Visual Basic 프로젝트를 함께 개발할 수 없다는 것이다. 하지만
                                                                                       21


                     Visual Studio .NET에서는 가능하다. 즉, 하나의 Visual Studio .NET을 실행한 상태에
                     서 동시에 Visual C++ 프로젝트와 Visual Basic 프로젝트를 개발할 수 있다.

                     물론 서로 다른 개발 툴을 함께 사용할 필요가 있느냐고 반문할 수도 있지만, 필자의 경험
                     으로는 함께 사용해야 하는 경우가 있다. 필자가 참여했던 프로젝트 중 Visual C++로
                     COM Object를 만들고, 이것을 Visual Basic에서 사용하는 애플리케이션으로 개발한 적
                     이 있는데, 여러 개의 Visual C++와 Visual Basic을 실행해 놓고 작업했던 기억이 난다.
                     이 때 각 프로젝트를 동시에 관리할 수 있는 기능이 있었다면 조금은 편리했을 것이다.

                     [그림 1-8]은 Visual Studio .NET을 두 번 실행해서 Visual C++ .NET 프로젝트와
                     Visual Basic .NET 프로젝트를 각각 개발하고 있는 화면이다.


        그림 1-8
Visual C++와 Visual
   Basic 프로젝트를
별도로 개발하는 화면




                     그리고 [그림 1-9]는 하나의 Visual Studio .NET에서 Visual C++ 프로젝트와 Visual
                     Basic 프로젝트를 동시에 개발하고 있는 화면이다.

                     [그림 1-9]에서 Class View를 보면 Visual C++ 프로젝트와 Visual Basic 프로젝트를 선
                     택하기 위한 항목이 각각 존재하는 것을 확인할 수 있다. 이 때 Visual C++ .NET은 각각
                     의 프로젝트를 솔루션(Solution)이라는 그룹으로 관리하며, Visual C++ 6.0은 워크스페이
                     스(Workspace)라는 그룹으로 관리한다.
22


             그림 1-9
     Visual C++와 Visual
        Basic 프로젝트를
     동시에 개발하는 모습




                          하나의 솔루션과 워크스페이스는 여러 개의 프로젝트를 포함해 관리할 수 있지만, Visual
                          C++ 6.0의 워크스페이스는 Visual C++ 프로젝트만 포함할 수 있다. 솔루션과 워크스페
                          이스에 대한 자세한 내용은 추후에 설명하겠다.

                          이와 같이 Visual C++ .NET은 Visual C++ 6.0에 비해 향상된 기능을 제공하며, 서로 다
                          른 툴을 이용하는 여러 개의 프로젝트를 동시에 관리할 수 있는 등 비교적 큰 규모의 프로
                          젝트에 유리하다. 하지만 그러한 기능을 이용하는 애플리케이션을 개발하지 않는다면 굳
                          이 Visual C++ .NET을 이용할 필요는 없을 것 같다. 설치해 본 독자는 알겠지만 Visual
                          C++ 6.0은 CD 한 장으로 간단히 설치할 수 있지만, Visual C++ .NET을 설치하려면 여
                          러 장의 CD가 필요하며, 설치 과정도 그리 간단하지 않다.

                          또한 각 개발 환경을 실행해 보면 알겠지만 Visual C++ 6.0이 비교적 낮은 사양의 컴퓨
                          터에서도 무리없이 실행되는 반면, Visual C++ .NET은 비교적 높은 사양의 컴퓨터를 필
                          요로 한다. 따라서 독자의 컴퓨터 사양에 맞는 버전의 Visual C++를 적절히 선택할 필요
                          가 있다.
                                                                 23



Visual C++ .NET의 설치
Visual C++ .NET의 설치는 Visual C++ 6.0에 비해 조금 복잡한 편이다. Visual C++
.NET 설치에 어려움을 느끼는 독자는 다음 웹 사이트를 참조하기 바란다. 필자가 운영을
담당하고 있는 Visual C++ 관련 온라인 강좌에서 참고자료로 작성해 두었던 Visual C++
.NET 설치과정을 볼 수 있다.


 http://www.sds.pe.kr/redirectURL?index=2


지금까지 윈도우 애플리케이션 개발 툴인 Visual C++에 대해 알아보았다. 다음 장에서는
Visual C++의 메뉴와 개발을 편리하게 해주는 여러 가지 윈도우 등 보다 세부적인 구성
에 대해 학습한다.
24
                           Visual C++의 주요 구성
                           이 장에서는 Visual C++를 이용해서 프로그램을 작성하기 위해 필요한 사항들을 알아보도록 하
                           자. 다른 윈도우 애플리케이션도 마찬가지겠지만 Visual C++도 명령을 내리기 위한 메뉴, 그리
                           고 기타 인터페이스 개체들과 사용자에게 정보를 제공하기 위한 윈도우 등의 영역으로 구성되며,
Chapter 02                 통합개발환경이므로 프로그램 소스를 편집하기 위한 에디터가 내장되어 있다.




                            Visual C++의 메뉴 구성

                            Visual C++의 메뉴는 기본 메뉴와 상황에 따라 제공되는 메뉴로 나눌 수 있다. 예를 들어
                            [File], [Edit], [View] 등의 메뉴는 Visual C++의 상태와 무관하게 항상 나타나는 메뉴이
                            다. 프로젝트의 상황에 맞게 별도로 제공되는 메뉴는 주로 특정한 기능에 의존적인 경우가
                            많으므로 해당 기능을 학습할 때 알아보도록 하고 여기서는 기본 메뉴에 대해서만 알아보
                            도록 하자.

                            Visual C++는 프로젝트가 오픈되지 않은 상태에서는 [File], [Edit], [View], [Tools],
                            [Window], [Help] 메뉴가 나타나고, 프로젝트가 오픈되면 [Project], [Build], [Debug]
                            메뉴가 추가로 나타난다. [그림 2-1]은 프로젝트가 오픈된 상태의 메뉴 바이다.


              그림 2-1
     Visual C++ .NET의 메뉴


                            이 그림은 Visual C++ .NET의 메뉴인데, 6.0도 큰 차이는 없다. 다만 [Debug] 메뉴가
                            [Build] 메뉴의 서브 메뉴로 존재한다는 점 정도가 다르다.




                            [File] 메뉴
                            [File] 메뉴는 새로운 프로젝트를 생성하거나 기존의 프로젝트에 새로운 파일을 추가하는
                            메뉴 등으로 구성되어 있으며, 프로젝트의 오픈, 닫기, 저장 메뉴도 포함되어 있다. {File]
                            메뉴의 서브 메뉴들은 다음과 같은 기능을 한다.


                              ∙ New: 새로운 프로젝트나 파일을 생성한다.

                              ∙ Open: 기존에 존재하는 프로젝트나 파일을 오픈한다.

                              ∙ Close: 열려 있는 파일을 닫는다.
                                                      25


 ∙ Add New Item: 새로운 파일을 프로젝트에 추가한다.

 ∙ Add Existing Item: 존재하는 파일을 프로젝트에 추가한다.

 ∙ Add Project: 현재 솔루션에 프로젝트를 추가한다.

 ∙ Open Solution: 솔루션을 오픈한다.

 ∙ Close Solution: 솔루션을 닫는다.

 ∙ Save: 지정된 파일을 저장한다.

 ∙ Save 파일명 As: 지정된 파일을 새로운 이름으로 저장한다.

 ∙ Save All: 열려 있는 모든 파일을 저장한다.

 ∙ Source Control: 버전 관리를 위한 메뉴가 포함되어 있다.

 ∙ Page Setup: 출력 설정을 위한 Page Setup 대화상자를 표시한다.

 ∙ Print: 현재 선택된 파일을 출력한다.

 ∙ Recent Files: 최근에 작업한 파일 목록을 보여준다.

 ∙ Recent Projects: 최근에 작업한 프로젝트 목록을 보여준다.

 ∙ Exit: Visual C++ .NET을 종료한다.




[Edit] 메뉴
[Edit] 메뉴는 일반 윈도우 애플리케이션의 메뉴와 거의 동일하며, 소스 편집과 관련된 메
뉴가 포함되어 있다.


 ∙ Undo: 이전에 작업한 내용을 취소한다.

 ∙ Redo: Undo로 취소했던 작업을 다시 수행한다.

 ∙ Cut: 선택된 영역을 잘라낸다. 잘라낸 영역은 클립보드에 복사된다.

 ∙ Copy: 선택된 영역을 복사한다.

 ∙ Paste: 클립보드의 내용을 붙여 넣는다.

 ∙ Delete: 문자열을 삭제한다.

 ∙ Find & Replace: 문자열 검색이나 치환을 수행한다.

 ∙ Insert File As Text: 선택된 파일을 현재 열려있는 파일에 삽입한다.
26


     [View] 메뉴
     [View] 메뉴는 Visual C++ .NET의 주요 구성 요소를 보기 위한 메뉴로 구성되어 있다.
     이 장의 후반부에서 설명할 Solution Explorer나 Class View를 선택할 수 있다.


      ∙ Open: Solution Explorer 또는 Class View에서 선택한 대상(멤버함수 또는 파일 등)을 오픈
        한다.

      ∙ Open With: Solution Explorer 또는 Class View에서 선택한 대상을 선택한 편집기를 이
        용해서 오픈한다.

      ∙ Solution Explorer: Solution Explorer를 오픈한다.

      ∙ Class View: Class View를 오픈한다.

      ∙ Server Explorer: Server Explorer를 오픈한다.

      ∙ Resource View: Resource View를 오픈한다.

      ∙ Properties Window: Properties Window를 오픈한다.

      ∙ Toolbox: Toolbox를 오픈한다.

      ∙ Toolbars: Visual C++ .NET의 Toolbar 설정을 변경한다.

      ∙ Full Screen: Visual C++ .NET을 전체 화면으로 실행한다.

      ∙ Property Pages: 속성 페이지를 표시한다.




     [Project] 메뉴
     [Project] 메뉴는 현재 선택되어 있는 프로젝트에 클래스, 파일 또는 리소스를 추가할 때
     사용한다.


      ∙ Add Class: 프로젝트에 클래스를 추가한다.

      ∙ Add Resource: 프로젝트에 비트맵, 커서 등의 리소스를 추가한다.

      ∙ Add New Item: 프로젝트에 새로운 파일, 리소스 등을 추가한다.

      ∙ Add Existing Item: 프로젝트에 이미 존재하는 파일, 리소스 등을 추가한다.

      ∙ Unload Project/Reload Project: 솔루션에서 프로젝트를 Unload/Reload한다.

      ∙ Set as StartUp Project: 현재 프로젝트를 시작 프로젝트로 설정한다.

      ∙ Properties: 속성 페이지를 표시한다.
                                                              27



[Build] 메뉴
프로젝트 빌드에 관련된 메뉴이다. 빌드 명령을 프로젝트 단위 또는 솔루션 단위로 수행할
수 있다.


 ∙ Build Solution: 솔루션에 포함된 프로젝트의 변경된 파일을 저장한 후, 컴파일과 링크 작
   업을 수행한다. 변경되지 않은 파일은 컴파일되지 않는다.

 ∙ Rebuild Solution: 솔루션에 포함된 프로젝트의 모든 파일을 컴파일한 후, 링크 작업을 수
   행한다.

 ∙ Clean Solution: 솔루션에 포함된 모든 프로젝트의 빌드 과정에서 생성된 임시 파일을 제
   거한다.

 ∙ Build 프로젝트 명: 프로젝트의 변경된 파일을 저장한 후, 컴파일과 링크 작업을 수행한다.
   변경되지 않은 파일은 컴파일되지 않는다.

 ∙ Rebuild 프로젝트 명: 프로젝트의 모든 파일을 컴파일한 후, 링크 작업을 수행한다.

 ∙ Clean 프로젝트 명: 프로젝트의 빌드 과정에서 생성된 임시 파일을 제거한다.

 ∙ Batch Build: Batch Build 대화상자에서 지정한 프로젝트를 Batch로 빌드한다.

 ∙ Configuration Manager: 프로젝트의 설정을 변경한다.




[Debug] 메뉴
[Debug] 메뉴는 프로젝트의 디버깅에 관련된 메뉴로 구성되어 있다. 디버깅에 대한 자세
한 내용은 11장에서 실제 애플리케이션을 이용해 알아본다.


 ∙ Start: 디버그를 시작한다.

 ∙ Start Without Debugging: 애플리케이션을 실행한다. 프로젝트가 빌드되어 있지 않으면
   빌드 작업을 먼저 수행한다.

 ∙ Processes: 프로세스 대화상자를 표시한다.

 ∙ Exceptions: 디버깅 작업시 예외처리에 대한 내용을 설정한다.

 ∙ Step Into: 디버깅 작업시 함수의 내부로 이동하여 디버깅 작업을 진행한다.

 ∙ Step Over: 디버깅 작업시 함수를 실행한 후 함수 다음 위치에서 디버깅 작업을 계속 진행
   한다.
28

      ∙ Step Out: 디버깅중인 함수 외부로 제어를 이동한 후, 디버깅 작업을 계속 진행한다.

      ∙ Quick Watch: Quick Watch 윈도우를 표시한다. 변수를 포함한 표현식의 값을 모니터링
        할 수 있다.

      ∙ Stop Debugging: 디버깅을 종료한다.

      ∙ Restart: 현재의 디버깅 세션을 종료하고 디버깅을 다시 시작한다.

      ∙ New Breakpoint: 새로운 중단점을 설정한다.




     [Tools] 메뉴
     [Tools] 메뉴는 Visual C++ .NET에서 사용할 수 있는 여러 가지 Utility 프로그램을 실행
     하기 위한 메뉴와 옵션을 설정하기 위한 메뉴로 구성되어 있다.


      ∙ Customize: 툴바와 메뉴의 옵션을 지정하기 위한 대화상자를 표시한다.

      ∙ Options: Visual C++ .NET의 실행 환경에 대한 옵션을 설정한다.




     [Window] 메뉴
     일반 윈도우 애플리케이션의 [Window] 메뉴와 동일하며, 윈도우의 Docking, Floating 등
     을 설정한다.




     Explorer, View, Window
     Visual C++는 현재 개발되고 있는 프로젝트의 상태를 여러 윈도우를 통해 알려주는데, 형
     태에 따라 Explorer, View, Window 등의 이름으로 제공된다. 하나씩 알아보도록 하자.




     Solution Explorer
     프로젝트에 포함되어 있는 파일을 소스 파일, 헤더 파일, 리소스 파일 등으로 분류해서 보
     여준다. [그림 2-2]는 프로젝트명이 C++PJT인 프로젝트의 Solution Explorer이다.
                                                                                          29



       그림 2-2
  Solution Explorer




                      화면을 보면, 가장 상위 노드에 솔루션명이 나타나고, 하위 노드에 프로젝트명이 나타나
                      며, 프로젝트 노드의 하위 노드에 소스 파일, 헤더 파일, 리소스 파일이 분류되어 있는 것
                      을 볼 수 있다. 목록에 있는 파일명을 더블클릭하면 해당 파일이 오픈되어 편집 작업을 수
                      행할 수 있다. 또 각 노드를 마우스 오른쪽 버튼으로 클릭하면 소스 파일, 헤더 파일 등을
                      새로 작성하거나 추가할 수 있는 팝업 메뉴가 나타난다. [그림 2-3]은 Source Files 노드
                      에서 마우스 오른쪽 버튼을 클릭했을 때의 화면이다.


       그림 2-3
Solution Explorer의
          팝업 메뉴




                      이 메뉴에서 [Add] - [Add New Item] 메뉴를 선택하면 새로운 파일을 추가할 수 있는 대
                      화상자가 나타나며, 여기서 추가할 파일의 종류를 선택하고 파일명을 지정하면 새로운 파
                      일을 프로젝트에 추가할 수 있다.

                      Solution Explorer는 Visual C++ .NET에서 사용하는 이름이며, Visual C++ 6.0에서는
                      Workspace에 FileView라는 이름의 탭으로 존재한다.
30


                          Class View
                          프로젝트를 구성하고 있는 클래스의 목록을 보여준다. 클래스명 왼쪽의 확장 버튼을 누르
                          면 해당 클래스의 멤버함수와 멤버변수가 나타난다. Solution Explorer와 마찬가지로 멤
                          버함수나 멤버변수를 더블클릭하면 멤버함수와 멤버변수를 정의하고 있는 소스 파일과 헤
                          더 파일이 오픈된다.


             그림 2-4
             Class View




                          또한 Solution Explorer와 마찬가지로 클래스명을 마우스 오른쪽 버튼으로 클릭하면, 해
                          당 클래스에 대한 처리를 할 수 있는 메뉴가 나타난다. [그림 2-5]는 CCPJTView 클래스
                          를 마우스 오른쪽 버튼으로 클릭했을 때의 화면이다.


             그림 2-5
     Class View의 팝업 메뉴




                          여기서 [Add] - [Add Function] 메뉴를 선택하면 클래스의 멤버함수를 추가할 수 있고,
                          [Add Variable] 메뉴를 선택하면 멤버변수를 추가할 수 있다.




                          Server Explorer
                          Server Explorer는 Visual C++ .NET이 실행중인 컴퓨터와 네트워크로 연결되어 있는
                                                                                    31


                      서버의 정보를 보여준다. [그림 2-6]은 Server Explorer를 실행한 화면이다.


        그림 2-6
    Server Explorer




                      그림에서 PC1이라는 이름을 갖고 있는 컴퓨터가 Visual C++ .NET이 실행되고 있는 컴퓨
                      터이고, utopia라는 이름의 컴퓨터가 네트워크 상에 존재하는 서버(Windows 2000 Server)
                      컴퓨터이다. 각 서버명의 하위 노드에는 서버 관리를 위한 항목이 나열되어 있고, 이 항목
                      을 선택하면 세부 목록이 조회되므로 원격지에 존재하는 서버 관리를 쉽게 할 수 있다.

                      Servers 노드를 마우스 오른쪽 버튼으로 클릭하면 [Add Server] 메뉴가 나타나는데, 여기
                      서 Server Explorer에 새로운 서버를 추가할 수 있다(그림 2-7). 반대로 서버 이름을 마우
                      스 오른쪽 버튼으로 클릭하면 [Delete] 메뉴가 나타나는데, 이 메뉴를 선택하면 선택한 서
                      버를 Server Explorer에서 제거할 수 있다.


        그림 2-7
  Server Explorer에
서버를 추가하기 위한
          메뉴 선택
32

                        Server Explorer는 Visual C++ .NET에만 존재하며, Visual C++ 6.0에는 존재하지 않
                        는다.




                        Resource View
                        Resource View는 프로젝트에 포함되어 있는 리소스를 종류별로 보여준다. 리소스에는 대
                        화상자, 아이콘, 메뉴, 비트맵 파일 등이 포함된다. [그림 2-8]은 Resource View 화면을
                        보여주고 있다.


           그림 2-8
        Resource View




                        Resource View도 Solution Explorer나 Class View와 마찬가지로 마우스 오른쪽 버튼을
                        누르면 새로운 리소스를 추가할 수 있는 메뉴가 나타난다. [그림 2-9]는 아이콘 리소스를
                        선택하기 위해 Icon 항목을 마우스 오른쪽 버튼으로 클릭해서 팝업 메뉴가 나타나게 한 화
                        면이다.


           그림 2-9
     Resource View에서
        아이콘 리소스를
          추가하는 화면
                                                                                          33


                    [그림 2-9]에서 [Insert Icon] 메뉴를 선택하면 아이콘을 추가할 수 있다. 만약 아이콘이
                    아니라 프로젝트명을 마우스 오른쪽 버튼으로 클릭했다면, [Add] - [Add Resource]를 선
                    택하여 추가할 리소스의 종류를 지정한 후 추가할 수 있다. 즉, 리소스를 선택하지 않은
                    상태에서 마우스 오른쪽 버튼을 누르면 어떤 리소스를 추가할지 예측할 수 없으므로 [Add
                    Resource]라는 메뉴가 나타나고, 리소스를 선택한 상태에서 마우스 오른쪽 버튼을 누르면
                    선택한 리소스를 추가하는 것으로 간주하여 해당 리소스를 직접 추가하는 메뉴가 나타나
                    는 것이다.




                    Properties Window
                    Properties Window는 현재 선택되어 있는 대상(파일, 클래스, 리소스 등)이 갖고 있는 속성
                    을 보여준다. Solution Explorer, Class View, Resource View 등에서 파일, 클래스, 리
                    소스를 선택할 때마다 자동으로 해당하는 속성의 리스트가 Properties Window에 나타난
                    다. [그림 2-10]은 Class View에서 임의의 클래스를 선택했을 때 나타나는 Properties
                    Window이다.


      그림 2-10
Properties Window




                    Properties Window를 보면 선택된 클래스의 이름, 파일 위치 등이 목록으로로 나타나는
                    것을 볼 수 있다. Properties Window는 Visual C++ 6.0에서 Visual C++ .NET으로 업
                    그레이드되면서 가장 많은 기능이 추가된 부분이다.

                    Properties Window에서 메시지에 대한 메시지 핸들러나 메뉴를 선택했을 때의 처리를 기
                    술하는 명령 핸들러 작성까지 할 수 있다. Visual C++ 6.0에서 이런 기능을 처리하려면
                    Class Wizard나 Class View를 이용해야 했다. [그림 2-11]은 Properties Window에서 메
                    시지 핸들러를 추가하는 화면이다.
34


              그림 2-11
     Properties Window에서
            메시지 핸들러를
              추가하는 화면




                           Solution Explorer나 Class View가 주로 프로젝트의 구성 내역을 보는 기능을 담당하는
                           데 반해, Properties Window는 소스 코드의 작성(주로 자동으로 추가되는 메시지 핸들러나
                           명령 핸들러, 멤버함수 재정의(Overrides) 등)을 주 기능으로 하고 있다. 따라서 Properties
                           Window의 기능은 자세히 알아 둘 필요가 있다. 구체적인 사용법은 예제 프로젝트를 만들
                           면서 설명하도록 하겠다.




                           Output Window
                           Output Window는 컴파일이나 빌드 작업을 할 때 나타나는 실행 결과를 표시하기 위한
                           윈도우이다. [그림 2-12]는 프로젝트를 빌드했을 때 실행 결과가 나타난 화면이다.


              그림 2-12
           Output Window




                           지금까지 설명한 Explorer나 View, Window는 표시되는 형태를 다양하게 지정할 수 있다.
                           각 Window의 타이틀 바에서 마우스 오른쪽 버튼을 누르면 Dockable, Hide, Float, Auto
                                                                                          35


               Hide를 선택할 수 있는 메뉴가 나타난다. 예를 들어, Float 상태로 지정하면 Window가
               Floating 상태로 보이게 된다. 또 대부분의 윈도우 애플리케이션이 그렇듯이 타이틀 바를
               드래그하면 Visual C++ .NET의 어느 곳으로도 이동할 수 있다. 특히 Auto Hide 기능을
               선택하면 숨겨져 있다가 필요할 때만 보이기 때문에 화면이 좁을 경우 유용하게 사용할 수
               있다.




               에디터
               Visual C++의 에디터는 일반적인 에디터보다 다양한 기능을 제공한다. 객체의 멤버를 자
               동으로 보여주는 기능, 함수의 정보(인자, 반환형) 및 클래스, 변수의 정보를 동적으로 보여
               주는 IntelliSense 기능을 포함해 편집을 편리하게 해주는 여러 가지 기능이 제공된다.



               List Members
               에디터에 작성되고 있는 소스 코드를 실시간으로 분석하여 사용 가능한 클래스의 멤버변
               수와 멤버함수의 목록을 리스트 형태로 제공하는 기능이다. 객체의 멤버를 사용할 때 멤버
               의 정보를 쉽게 찾을 수 있고, 목록에서 선택하면 바로 입력되므로 소스 코드를 쉽게 작성
               할 수 있다. 또 함수에 대한 주석도 함께 나타난다.

               [Edit] - [IntelliSense] - [List Members] 메뉴를 선택하거나 [Ctrl] - [J] 키를 누르면 실
               행되며, 클래스의 멤버함수인 경우에는 멤버 접근 연산자인 . 이나 ->를 입력하면 자동으
               로 나타난다. [그림 2-13]은 List Members 기능을 이용해서 CString 클래스의 멤버함수
               를 입력하는 화면이다.


 그림 2-13
List Members
36


                      Parameter Info
                      함수를 사용할 때 사용하고 있는 인자에 대한 정보를 보여주는 기능으로, 함수에 사용되는
                      인자가 툴팁으로 표시된다. 만약 함수가 중복 정의되어 있다면 상하 화살표를 이용하여 중
                      복 정의된 인자 목록을 선택할 수도 있다. [Edit] - [IntelliSense] - [Parameter Info] 메
                      뉴를 선택하거나 [Ctrl] - [Shift] - [Space] 키를 누르면 실행되며, 함수의 인자를 입력하
                      기 위해 왼쪽 괄호를 입력해도 자동으로 실행된다. [그림 2-14]는 Parameter Info 기능
                      을 사용해서 함수의 인자를 입력하는 화면이다.


       그림 2-14
     Parameter Info




                      Quick Info
                      클래스나 함수 또는 변수 위로 마우스 커서를 이동하면 해당 대상의 정보를 툴팁으로 보여
                      주는 기능이다. 클래스 이름 위로 마우스를 이동하면 클래스에 대한 정보가 나타나고, 함
                      수 위로 커서를 이동하면 함수의 반환형과 인자 정보가 나타나며, 변수 위로 커서를 이동
                      하면 변수의 자료형이 나타난다. [Edit] - [IntelliSense] - [Quick Info] 메뉴를 선택하거
                      나 [Ctrl] - [K],[I] 키를 누르면 실행된다. [그림 2-15]는 함수의 반환형과 인자가 표시된
                      화면이다.


       그림 2-15
         Quick Info
                                                                                    37



                Complete Word
                자동으로 문맥을 완성하는 기능이다. 예를 들어, 함수 이름을 입력할 때 처음 몇 글자만
                입력한 후 이 기능을 선택하면 입력 가능한 목록이 나타난다. 함수뿐 아니라 변수나 클래
                스 이름을 입력할 때도 사용할 수 있다. 만약 적용 가능한 단어가 하나밖에 없다면 그 단
                어를 바로 입력해 준다. [Edit] - [IntelliSense] - [Complete Word] 메뉴를 선택하거나
                [Alt] 키와 함께 오른쪽 화살표 키를 누르면 실행된다. [그림 2-16]은 Complete Word 기
                능을 이용하는 화면이다.

   그림 2-16
Complete Word




                Outlining
                소스 코드가 길어지면 가독성이 떨어져서 편집이 불편해진다. 이 때 Outlining 기능을 이
                용하면 선택한 블록을 숨기거나 표시할 수 있어 편집 작업이 편리해진다.[Edit] -
                [Outlining] 메뉴에서 지정할 수 있으며, Outlining 기능을 적용할 부분을 블록으로 선택
                한 다음, [Hide Selection] 메뉴를 선택하면 적용된다. [그림 2-17]부터 [그림 2-19]까지
                는 Outlining 기능을 적용하기 전과 적용한 후의 소스 코드 편집 화면이다.


   그림 2-17
Outlining 기능을
     적용하기 전
38


           그림 2-18
       Outlining 기능을
     적용한 후(선택 영역이
            숨겨진 상태)




           그림 2-19
        Outlining 기능을
               적용한 후




                          Navigation Bar
                          프로젝트에서 사용하고 있는 클래스에 멤버함수가 많으면, 편집할 멤버함수를 찾는 것도
                          귀찮은 작업이 될 수 있다. 물론 Class View에 나타나는 멤버함수의 목록에서 찾고자 하
                          는 멤버함수의 이름을 더블클릭하는 방법도 있지만, 에디터에서 클래스의 멤버함수를 쉽
                          게 찾는 방법이 있다. [그림 2-20]처럼 에디터의 타이틀 바 아래에 클래스와 멤버함수의
                          목록이 나타나는 Navigation Bar가 있는 것을 볼 수 있다.


           그림 2-20
         Navigation Bar
                                                                     39


Navigation Bar의 멤버함수 목록에서 원하는 멤버함수를 선택하면 해당 멤버함수가 나타
난다. 만약 Navigation Bar 기능이 필요 없다면, [Tools] - [Options] 메뉴를 실행하여
Options 대화상자를 표시한 다음, [Text Editor] - [C/C++] - [General] 설정 내용을 찾
아 Display 항목에서 Navigation Bar의 체크를 해제하면 된다.




이상으로 Visual C++ .NET으로 애플리케이션을 개발하는 데 필요한 것들을 살펴보았다.
아직 확실하게 이해하지 못했더라도 걱정할 필요는 없다. 이후에 예제 애플리케이션의 작
성 방법을 설명할 때 이번 장에서 설명한 여러 가지 기능을 사용하게 된다.
40
                        Application Wizard
                        Visual C++의 강력한 기능 중 하나가 바로 개발할 애플리케이션에 적합한 템플릿을 미리 만들어
                        주는 템플릿 자동 생성 기능이다. 일반적으로 새로운 애플리케이션을 만들 때 완전한 백지 상태
                        에서 코딩을 시작하는 경우는 거의 없으며, 이전에 작성했던 비슷한 유형의 프로그램을 참조해서

Chapter 03              작업하는 경우가 많다. 이번 장에서는 개발할 애플리케이션의 기본 코드를 자동으로 생성해 주는
                        Application Wizard에 대해 배운다.




                          Application Wizard
                          Application Wizard가 어떤 기능을 하는지 알아보고, 실제 예를 들어 보도록 하자.




                          개요
                          Visual C++를 이용하면 다양한 환경에서 사용 가능한 애플리케이션을 개발할 수 있다.
                          Win32 API 애플리케이션이나 MFC(Microsoft Foundation Class) 라이브러리를 사용하는
                          애플리케이션, DLL 제작 등 여러 가지 개발 형태가 있을 수 있는데, Visual C++는 각 애
                          플리케이션별로 프로젝트 템플릿을 자동으로 생성할 수 있는 기능을 제공한다. 또한 획일
                          적으로 정형화된 코드만 생성하는 것이 아니라 프로젝트의 성격, 특성별로 다양한 옵션을
                          줄 수 있기 때문에 개발하려는 프로젝트의 성격에 맞는 소스 템플릿을 간단한 옵션 설정만
                          으로도 얻을 수 있다. [표 3-1]은 Visual C++ .NET을 이용하여 생성할 수 있는 프로젝트
                          유형을 정리한 것이다.


            표 3-1          프로젝트 유형                          설명
     Visual C++ .NET의
                           ATL Project                      COM 오브젝트를 포함하는 ATL 프로젝트 생성
          프로젝트 유형
                           ATL Server Project               ATL Server Library를 사용하는 프로젝트 생성
                           ATL Server Web Service Project   ATL Server Library를 사용하는 XML Web Service 프로젝트 생성
                           Makefile Project                 Makefile 프로젝트 생성
                           Managed C++ Application          Managed Extension을 사용하는 C++ Application 제작
                           Managed C++ Class Library        Managed Extension을 사용하는 C++ Class Library 제작
                           Managed C++ Empty Project        Managed Extension을 사용하는 빈 프로젝트 생성
                           Managed C++ Web Service          Managed Extension을 사용하는 XML Web Service 제작
                           MFC ActiveX Control              MFC ActiveX Control 제작
                           MFC Application                  MFC Library를 사용하는 Application 제작
                           MFC DLL                          MFC Library를 사용하는 DLL 제작
                           Win32 Project                    Win32 Console Application 또는 Win32 Application 제작
                                                                                              41


                  표의 내용 중 ATL과 Managed C++ 관련 프로젝트는 Visual C++ .NET에서만 생성 가능
                  한 프로젝트 유형으로, XML Web Service와 .NET 기반 애플리케이션을 개발할 때 선택
                  한다.

                  Visual C++ 6.0을 사용할 경우, 프로젝트 유형 자체는 .NET과 거의 유사하나 세부적으
                  로는 약간의 차이가 있다. 우선 Visual C++ 6.0은 .NET을 지원하지 않으므로 XML Web
                  Service나 Managed C++ 관련 프로젝트를 지원하지 않는다. 그리고 Visual C++ .NET
                  에서 Win32 Project 하나로 제공되었던 프로젝트 유형이 Visual C++ 6.0에서는 Win32
                  Application, Win32 Console Application, Win32 Dynamic-Link Library, Win32
                  Static Library의 네 가지로 제공된다. 하지만 Visual C++ .NET에서도 Win32 Project를
                  선택하고 세부 옵션으로 들어가면, Console Application, Windows Application, DLL,
                  Static Library를 선택할 수 있으므로 결과적으로는 동일한 프로젝트 유형을 제공한다고
                  할 수 있다.




                  Application Wizard를 이용한 프로젝트 템플릿 생성
                  Application Wizard를 이용해서 프로젝트 템플릿을 생성하는 방법을 배워보자. 프로젝트
                  의 생성은 문답식으로 몇 번의 단계를 거쳐 이루어진다. 우선 생성할 프로젝트의 유형을
                  선택해야 하는데, 가장 일반적으로 사용되는 Win32 Application, MFC Application 프로
                  젝트 중에서 MFC Application을 예로 들어 설명하겠다. 또한 Visual C++ .NET과
                  Visual C++ 6.0의 프로젝트 생성 방법이 조금 다르므로, 6.0으로 프로젝트를 생성하는
                  방법도 함께 설명한다.

                  먼저 Visual C++ .NET의 프로젝트 생성 방법부터 설명하겠다. [File] - [New] - [New
                  Project] 메뉴를 실행하거나 [Ctrl] - [Shift] - [N] 키를 누른다. 그러면 프로젝트 생성을
                  위한 New Project 윈도우가 나타날 것이다(그림 3-1).


      그림 3-1
New Project 윈도우
42

                          오른쪽 그림의 목록은 New Project 윈도우의 Templates에 있는 리스트가 모두 보이도록
                          확장한 화면이다. Project Types에서 Visual C++ 프로젝트를 선택한 다음, Templates에
                          서 MFC Application을 선택한다. 프로젝트명(Name)에는 WizardTest를 입력하고, 프로젝
                          트가 생성될 경로를 지정한 다음, [OK] 버튼을 눌러 세부 옵션을 지정하기 위한 단계로 진
                          행한다. 그러면 [그림 3-2]처럼 Application Wizard가 나타날 것이다.


            그림 3-2
     Application Wizard




                          [그림 3-2]는 생성할 프로젝트의 개요(Overview)가 나타난 것으로, 프로젝트의 설정을 변
                          경하려면, Overview 밑에 있는 Application Type이나 Database Support 등의 원하는
                          항목을 선택하면 된다. [그림 3-3]은 Application Type의 세부 옵션이다.


            그림 3-3
      Application Type
        프로젝트 설정
                                                                                                               43


                      Application Type에서는 생성될 애플리케이션의 구조를 결정하는 옵션을 설정하게 되는
                      데, 각 옵션에 대한 설명은 [표 3-2]에 나와 있다.


          표 3-2       옵션                     설명
Application Type 옵션
                                             생성할 애플리케이션의 유형을 선택한다. 다음과 같은 값을 선택할 수 있다.
                                              ․ Single document: SDI(Single Document Interface) 애플리케이션.
                      Application type
                                              ․ Multiple documents: MDI(Multiple Document Interface) 애플리케이션.
                                              ․ Dialog based: 대화상자 기반 애플리케이션.

                                             애플리케이션의 스타일을 선택한다.
                      Project style           ․ Windows Explorer: 윈도우 탐색기 같은 인터페이스를 갖는 애플리케이션.
                                              ․ MFC standard: 일반적인 MFC 애플리케이션.

                                             MFC 라이브러리를 사용하는 방법을 지정한다.
                      Use of MFC              ․ Use MFC in a shared DLL: MFC 라이브러리를 공유 DLL로 사용함.
                                              ․ Use MFC in a static library: MFC 라이브러리를 정적으로 링크함.
                      Document/View          생성할 애플리케이션이 Document/View 아키텍처를 지원할 것인지의 여부를
                      architecture support   선택한다.
                      Resource language      사용할 언어를 선택한다. 시스템에 설치되어 있는 언어가 목록으로 제시된다.



                      기본으로 선택되어 있는 옵션은 MFC MDI(Multiple Document Interface) 애플리케이션일
                      것이다. MDI 애플리케이션은 하나의 애플리케이션에서 여러 개의 Document를 가질 수
                      있다는 것을 의미하는데, MDI, Document 등에 대한 내용은 이후 단원에서 자세히 설명
                      되니 여기서는 프로젝트를 생성하기 위한 옵션이라고만 알아두기로 하자.

                      다음으로 [그림 3-4]는 Compound Document Support의 세부 옵션이다.


         그림 3-4
 Compound Document
Support 프로젝트 설정
44

                           Compound Document Support에서는 OLE(Object Linking & Embedding) 기능과 관련된
                           옵션을 설정하는데, 다음과 같은 값을 지정할 수 있다.


               표 3-3       옵션                   설명
     Compound Document
                                                OLE 설정과 관련된 옵션을 지정한다.
           Support 옵션
                                                 ․ None: OLE를 지원하지 않는 프로젝트를 생성.
                                                 ․ Container : 복합 문서를 지원하는 OLE 컨테이너 애플리케이션을 생성.
                           Compound document     ․ Mini server : OLE 서버 애플리케이션을 생성. 단독으로는 실행이 불가능하
                           support                 고 다른 대상에 포함되는 방법만 있음.
                                                 ․ Full server : OLE 서버 애플리케이션을 생성. 단독으로 실행 가능.
                                                 ․ Container/Full server : OLE 컨테이너와 서버를 모두 지원하는 애플리케이
                                                   션을 생성.
                                                Compound document options 설정에 따른 추가 옵션을 지정한다.
                                                 ․ Active document server : Mini server, Full server, Container/Full server
                                                   옵션이 선택되면 활성화됨. 이 옵션을 선택하면 Active document를 생성할
                                                   수 있음.
                           Additional options
                                                 ․ Active document container : Container, Container/Full server 옵션이 선
                                                   택되면 활성화됨. 이 옵션을 선택하면 Active document를 포함할 수 있는
                                                   컨테이너 애플리케이션이 생성됨.
                                                 ․ Support for compound files : 복합 파일을 지원함.



                           Compound Document Support 옵션은 Application type에서 Document/View archi-
                           tecture support 항목이 설정되어 있어야 활성화된다.

                           다음으로, Document Template Strings의 세부 옵션을 살펴보자(그림 3-5).


              그림 3-5
       Document Template
     Strings 프로젝트 설정
                                                                                                45


                    Document Template Strings 옵션에서는 Document Template에 대한 문자열 속정을 정
                    의하며, Compound Document Support와 마찬가지로 Application Type에서 Document/
                    View architecture support 항목이 설정되어 있어야 활성화된다. 이 옵션은 Nonlocalized
                    string과 Localized string으로 나누어지는데, 세부 옵션은 [표 3-4]에 설명하였다.


        표 3-4       옵션                       설명
Document Template
                    <Nonlocalized strings>
      Strings 옵션
                    File extension           애플리케이션에서 사용하는 데이터 파일의 확장자를 지정한다.

                    File type ID             시스템 레지스트리에 사용되는 Document 유형 ID를 지정한다.

                    <Localized strings>

                                             Localized String에 사용되는 언어를 지정한다. 다른 언어로 변경하려면
                    Language
                                             Application Type의 Resource language에서 변경해야 한다.

                    Main frame caption       애플리케이션의 윈도우 타이틀 바에 나타날 문자열을 지정한다.

                    Doc type name            Document 유형을 식별하기 위한 이름을 지정한다.

                                             열기(Open) 대화상자와 저장(Save) 대화상자에서 파일 형식의 콤보 박스에 나
                    Filter name
                                             타나는 필터를 지정한다.

                                             [File] - [New] 대화상자에 사용되는 이름을 지정한다. 만약 애플리케이션이
                    File new short name      자동화 서버(Automation Server)라면 여기서 지정하는 이름은 자동화 객체의
                                             짧은 이름으로 사용된다.

                                             시스템 레지스트리의 파일 유형명으로 사용된다. 만약 애플리케이션이 자동화
                    File type long name
                                             서버라면 자동화 객체의 긴 이름으로 사용된다.



                    다음으로 데이터베이스 애플리케이션을 만들기 위한 옵션을 설정하는 Database Support
                    를 살펴보자(그림 3-6).


       그림 3-6
 Database Support
     프로젝트 설정
46

                           Database Support 옵션은 데이터베이스 관련 설정을 지정하기 위해 사용되며, [표 3-5]
                           와 같은 값을 지정할 수 있다.


               표 3-5
                           옵션                     설명
     Database Support 옵션
                                                  데이터베이스 지원 수준을 선택한다.
                                                   ․ None: 데이터베이스 기능을 사용하지 않는다.
                                                   ․ Header files only: 기본 기능만 가지는 데이터베이스 애플리케이션을 생성
                                                     한다.
                           Database support        ․ Database view without file support: 데이터베이스 헤더 파일, 라이브러리
                                                     파일, 그리고 Record View, Recordset을 사용하는 애플리케이션을 생성한
                                                     다. Document/View architecture가 설정되어 있어야 한다.
                                                   ․ Database view with file support: Database view without file support와 동
                                                     일하지만, Document 직렬화(Serialization) 기능이 지원된다.

                                                  애플리케이션이 사용할 데이터베이스 클래스를 선택한다.
                           Client type             ․ OLE DB: OLE DB Data Source를 사용하는 애플리케이션을 생성한다.
                                                   ․ ODBC: ODBC Data Source를 사용하는 애플리케이션을 생성한다.

                                                  Data Source를 선택한다.
                                                   ․ Data Link Properties: Client type에서 OLE DB를 선택했을 경우 데이터 연
                           Data source               결 속성을 지정한다.
                                                   ․ Select Data Source: Client type에서 ODBC를 선택했을 경우 데이터 원본을
                                                     지정한다.

                           Generated attributed   OLE DB가 선택되었을 경우에만 사용 가능하며, 생성되는 클래스가 속성
                           database class         (Attributes)을 사용하도록 지정한다.

                                                  ODBC가 선택되었을 경우에만 사용 가능하다. 선택된 테이블에 존재하는 모든
                           Bind all columns
                                                  칼럼과 연결된 멤버를 생성한다.

                                                  ODBC가 선택되었을 경우에만 사용 가능하다.
                           Type                    ․ Dynaset: Recordset으로 Dynaset을 선택한다.
                                                   ․ Snapshot: Recordset으로 Snapshot을 선택한다.



                           데이터베이스에 익숙하지 않은 독자는 지금 설명하는 내용이 생소하게 느껴질 수도 있다.
                           하지만 책의 후반부에 다루는 데이터베이스 단원에서 자세히 설명되니 두려워하지 말기
                           바란다.

                           다음으로 살펴 볼 옵션은 애플리케이션의 외형을 결정하는 User Interface Features이다
                           (그림 3-7).
                                                                                                       47



           그림 3-7
User Interface Features
          프로젝트 설정




                          User Interface Features 옵션은 주로 애플리케이션의 외형(최소화 버튼, 최대화 버튼, 메
                          뉴, 상태 바 등), 또는 사용자와 인터페이스하기 위한 대상에 관련된 설정을 위한 옵션이다.
                          [표 3-6]과 같은 옵션을 지정할 수 있다.


            표 3-6
                          옵션                   설명
        User Interface
        Features 옵션                            애플리케이션의 메인 윈도우에 관련된 옵션을 설정한다.
                                                ․ Thick frame: 윈도우의 크기 조절이 가능한 경계(Border)를 가질 것인지의 여
                                                  부를 결정한다.
                                                ․ Minimize box: 최소화 버튼의 표시 여부를 지정한다.
                                                ․ Maximize box: 최대화 버튼의 표시 여부를 지정한다.
                          Main frame styles     ․ Minimized: 애플리케이션이 아이콘 형태로 실행되도록 한다.
                                                ․ Maximized: 애플리케이션이 최대화된 상태로 실행되도록 한다.
                                                ․ System menu: 시스템 메뉴의 표시 여부를 지정한다.
                                                ․ About box: About 박스의 표시 여부를 지정한다.
                                                ․ Initial status bar: 상태바(Status Bar)의 표시 여부를 지정한다.
                                                ․ Split window: 분할 윈도우 사용 여부를 지정한다.

                                               애플리케이션의 차일드 프레임에 관련된 옵션을 설정한다. MDI 애플리케이션일
                                               경우에만 해당된다.
                                                ․ Child minimize box: 차일드 윈도우에 최소화 버튼을 표시할 것인지의 여부를
                                                  지정한다.
                          Child frame styles
                                                ․ Child maximize box: 차일드 윈도우에 최대화 버튼을 표시할 것인지의 여부
                                                  를 지정한다.
                                                ․ Child minimized: 차일드 윈도우가 최소화된 상태로 실행되도록 한다.
                                                ․ Child maximized: 차일드 윈도우가 최대화된 상태로 실행되도록 한다.
48


                         옵션                  설명

                                             애플리케이션이 툴바를 가질 것인지의 여부를 지정한다. 대화상자 기반 애플리케
                                             이션일 경우에는 해당되지 않는다.
                                              ․ None: 툴바를 갖지 않는 애플리케이션을 생성한다.
                         Toolbars
                                              ․ Standard docking: 윈도우 표준 툴바를 갖는 애플리케이션을 생성한다.
                                              ․ Browser style: Internet Explorer 스타일의 툴바를 갖는 애플리케이션을 생성
                                                한다.

                                             대화상자의 타이틀을 지정한다. 대화상자 기반의 애플리케이션일 경우에만 해당
                         Dialog title
                                             된다.



                         [그림 3-8]은 지금까지 설정한 옵션 외에 추가 기능을 선택하는 Advanced Features 옵션
                         이다.


            그림 3-8
     Advanced Features
          프로젝트 설정




                         Advanced Features 옵션에서는 사용자 인터페이스, 자동화(Automation), 통신(Windows
                         socket)을 포함한 고급 기능을 설정한다. 세부 옵션은 [표 3-7]과 같다.


             표 3-7       옵션                  설명
             Advanced
         Features 옵션                         ․ Context-sensitive Help: Context-sensitive help 작성에 필요한 도움말 파일을
                                               생성한다. WinHelp 포맷과 HTML Help 포맷 중에서 선택할 수 있다.
                                             ․ Printing and print preview: 인쇄, 프린터 설정, 미리 보기 등의 기능을 포함할
                         Advanced features     것인지 지정한다.
                                             ․ Automation: 자동화 기능의 포함 여부를 지정한다.
                                             ․ ActiveX controls: ActiveX 컨트롤의 지원 여부를 지정한다.
                                             ․ MAPI: Mail 기능의 지원 여부를 지정한다,
                                                                                                       49



                    옵션            설명

                                  ․ Windows sockets: TCP/IP 통신을 위한 Windows socket의 지원 여부를 지정
                                    한다,
                                  ․ Active Accessibility: 동적 접근성 지원 여부를 지정한다.
                                  ․ Common Control Manifest: Windows XP에서 지원하는 공통 컨트롤 지원 여부
                                    를 지정한다.
                                  ․ Number of files on recent file list: [File] 메뉴에 나타나는 ‘최근에 사용한 파일
                                    목록’에 보여 줄 파일의 수를 지정한다.



                    다음으로 Application Wizard가 생성할 파일과 클래스의 정보를 지정하는 Generated
                    Classes 옵션을 살펴보자(그림 3-9).


      그림 3-9
Generated Classes
    프로젝트 설정




                    Application Wizard는 선택한 옵션에 따라 애플리케이션 개발에 필요한 클래스를 자동으
                    로 생성한다. 이 때 프로젝트 이름에 기초하여 기본적인 클래스 이름과 파일명을 제시하
                    게 되는데, 이 옵션에서 지정된 이름을 변경할 수 있다. 클래스의 이름, 헤더 파일(.h) 이
                    름, 구현 파일(.cpp) 이름을 변경할 수 있으며, 기본으로 상속받는 기본 클래스를 변경할
                    수도 있다.




                    옵션 설정이 모두 끝났으면, 프로젝트를 생성하기 위해 [Finish] 버튼을 누른다. 그러면
                    현재 선택되어 있는 옵션을 이용해 지정된 경로에 프로젝트가 생성된다. Class View를 보
                    면, [그림 3-10]처럼 생성된 프로젝트를 구성하고 있는 클래스들이 보일 것이다.
50


            그림 3-10
         생성된 프로젝트를
     Class View로 본 모습




                        [그림 3-11]은 프로젝트가 생성된 폴더를 윈도우 탐색기로 확인한 화면이다.


            그림 3-11
       생성된 프로젝트의
            파일 구성




                        프로젝트 폴더에는 자동으로 생성된 소스 파일과 헤더 파일, 그리고 기타 프로젝트 관련
                        파일이 있는 것을 볼 수 있다. 이렇게 생성된 프로젝트의 템플릿에 필요한 기능을 추가시
                        켜 나가면 새로운 애플리케이션이 완성되는 것이다.

                        Visual C++ 6.0을 이용하는 것도 이와 크게 다르지 않다. Visual C++ .NET과 대부분 비
                        슷한 옵션을 설정할 수 있으나 옵션에 따라 위치가 다른 부분이 있고 옵션의 지정을 단계
                        별로 할 수 있다는 점이 다르다. [그림 3-12]는 Visual C++ 6.0에서 프로젝트를 생성하
                        는 화면이다.
                                                                                             51



      그림 3-12
Visual C++ 6.0의
    프로젝트 생성




                     이 대화상자에서 프로젝트 유형과 프로젝트 이름, 경로를 입력한 후 [OK] 버튼을 누른다.
                     그러면 [그림 3-13]처럼 Application Wizard가 나타난다.


      그림 3-13
 Visual C++ 6.0의
Application Wizard




                     Visual C++ 6.0의 Application Wizard는 Visual C++ .NET과 달리 단계(Step)별로 이루
                     어지기 때문에 타이틀 바에 현재 단계가 어디인지 표시되며, 각 단계의 이동은 [Back] 버
                     튼과 [Next] 버튼으로 이루어진다. 단계의 개수는 프로젝트 유형마다 다르다.
52

                    [그림 3-13]은 MFC AppWizard(exe) 프로젝트 생성 1단계로, Visual C++ .NET의
                    Application Type과 비슷한 내용으로 구성되어 있다. 어느 단계에서든지 [Finish] 버튼을
                    누르면 현재 선택된 옵션으로 프로젝트가 생성되며, 생성되기 전에 [그림 3-14]처럼 생성
                    될 프로젝트의 요약 정보를 보여준다. 이 요약 정보 화면에서 [OK] 버튼을 누르면 프로젝
                    트가 생성된다.


          그림 3-14
     생성될 프로젝트의 정보




                    이제 Application Wizard를 이용해서 프로젝트를 생성하는 방법에 대해 어느 정도 이해
                    되었을 것이다. 세부적으로 이해가 되지 않는 부분이 있더라도 걱정할 필요는 없다. 이어
                    지는 학습 과정에서 Win32 API 애플리케이션과 MFC 애플리케이션을 개발하기 위한 프
                    로젝트를 만들어 볼 것이다.




                    Class Wizard
                    Class Wizard는 Visual C++에서 제공되는 Wizard 중 하나로, 프로젝트에서 사용되는 클
                    래스를 관리하기 위한 것이다. Class Wizard를 이용하면 새로운 클래스의 작성, 클래스의
                    멤버함수나 변수 작성 등의 작업을 쉽게 처리할 수 있다. Class Wizard는 Visual C++
                    .NET에서는 없고, Visual C++ 6.0에서만 존재한다.
                                                                                  53


                   그렇다면 Visual C++ .NET에서는 Class Wizard가 제공하는 기능을 사용할 수 없는 것일
                   까? 물론 그렇지 않다. Visual C++ .NET에서는 Class Wizard의 기능이 2장에서 설명한
                   Class View, Properties Window 등의 윈도우에 적절히 분산되어 있다. 사실 Visual
                   C++ 6.0에서 사용하던 Class Wizard는 너무 많은 기능이 통합되어 있다고 할 수도 있다.

                   [그림 3-15]는 Visual C++ 6.0의 Class Wizard를 보여주고 있다.


     그림 3-15
Visual C++ 6.0의
    Class Wizard




                   [그림 3-15]에서 보는 것처럼, 클래스의 작성, 멤버변수나 멤버함수의 추가, 메시지 핸들
                   러의 작성 등 Visual C++ 6.0에서 수행해야 하는 많은 작업을 할 수 있도록 설계되어 있
                   으며, 각 기능은 탭을 통해 주제별로 분리되어 있다.

                   Class Wizard의 사용방법은 실제 예제 코드를 작성할 때 기능별로 설명하도록 하고, 여기
                   서는 Class Wizard가 어떤 기능을 수행하는지만 이해하고 넘어가자.




                   이것으로 Visual C++ 개요와 구성에 대해 어느 정도 설명된 것 같다. 물론 Visual C++
                   의 모든 것을 살펴보지는 않았지만, 자주 사용되는 기능에 대해서는 어느 정도 언급되었
                   다. 더 자세한 기능이나 추가 설명이 필요한 부분은 실제 코드를 작성하면서 설명하도록
                   하겠다.
54
             Win32 API 애플리케이션의 개요

             Visual C++로 윈도우 애플리케이션을 개발하는 방법에는 크게 두 가지가 있다. 하나는 Win32
             API를 이용하는 방법이고, 다른 하나는 MFC 라이브러리를 이용하는 방법이다. 이번 장에서는

Chapter 04   Win32 API에 대해 먼저 알아본다.




               윈도우 프로그래밍 모델
               Win32 API 애플리케이션을 설명하기 전에 윈도우 운영체제에서 실행되는 애플리케이션
               의 구조에 대해 알아보도록 하자. DOS 환경에서 텍스트 기반의 프로그래밍을 주로 해왔
               던 개발자나, UNIX 운영체제를 사용한다고 하더라도 X-Window가 아닌 Telnet을 이용해
               온 개발자는 윈도우 애플리케이션의 구조를 이해하기 위해 프로그래밍 모델의 개념을 바
               꿔야 할 필요가 있다.

               텍스트 기반의 애플리케이션(보통 main( ) 함수가 정의되는 프로그램)은 구조적인 방법으로
               설계되고 작성된다. 이런 애플리케이션은 실행에 영향을 주는 요소(주로 사용자의 키보드 입
               력)가 많지 않기 때문에 애플리케이션의 시작 시점과 작업의 수행 순서, 종료 시점을 예측
               할 수 있으며, 실행 흐름을 플로차트 등으로 표현할 수 있다.

               이에 비해 윈도우 애플리케이션은 좀더 복잡한 실행 구조를 갖는다. 윈도우 애플리케이션
               은 실행에 영향을 주는 요소가 더 다양하다. 예를 들어, 애플리케이션의 메뉴를 선택할 수
               도 있고, 마우스를 클릭할 수도 있으며, 심지어 다른 애플리케이션이나 운영체제의 제어를
               받아야 할 경우도 있을 수 있다. 따라서 애플리케이션의 실행 순서를 예측할 수 없고(애플
               리케이션 입장에서 어렵다는 뜻이다. 사람은 실행시키는 주체이므로 당연히 실행 순서를 알 수 있
               다), 명시적으로 정의하기도 어렵다.

               따라서 윈도우 애플리케이션은 실행에 영향을 주는 요소별로 처리하는 구조를 갖게 되는
               데, 이러한 요소를 이벤트(Event)라고 한다. 즉, 메뉴를 선택하는 행위나 마우스를 클릭
               하는 행위, 또는 다른 애플리케이션으로부터 전달되는 모든 신호를 이벤트로 간주하고, 각
               이벤트마다 고유의 처리 로직을 구현한 다음, 실제로 이벤트가 발생할 때마다 정의해 둔
               처리 로직을 실행하는 방식이 된다. [그림 4-1]은 이와 같은 실행 방식을 그림으로 도식화
               한 것이다.
                                                                             55



      그림 4-1
윈도우 애플리케이션의
       실행 방식




               이와 같은 처리방법을 이벤트 구동(Event Driven) 방식이라고 하는데, 애플리케이션 내부
               에서 이벤트는 메시지 형태로 사용된다. 메시지의 세부적인 처리방법은 5장과 12장에서
               예제를 통해 자세히 살펴볼 것이다.




               Windows API
               윈도우 운영체제에서 실행되는 애플리케이션들을 보면 한 가지 공통점을 찾을 수 있다.
               즉, 애플리케이션의 형태는 다르지만 기본적인 기능, 예를 들어 파일을 선택하기 위한 파
               일 열기 대화상자나 메시지 박스의 형태 및 기능은 대부분 동일하다는 것을 발견할 수 있
               다. 이것은 파일 열기 대화상자나 메시지 박스를 각 애플리케이션에서 독자적으로 구현하
               지 않고, 윈도우 운영체제에서 제공하는 표준 함수를 사용하고 있기 때문이다.

               이처럼 윈도우 애플리케이션에서는 기본적인 기능을 구현할 때 운영체제 수준에서 미리
               정의되어 있는 표준 함수를 사용할 수 있는데, 이것을 Windows API(Application Prog-
               ramming Interface)라고 한다.

               Windows API는 C 함수 형태로 제공되기 때문에 API를 이용해서 개발되는 애플리케이션
               은 대부분 C 언어가 이용된다. 따라서 C 언어를 컴파일할 수 있는 컴파일러가 있으면 개
               발 가능하다. 우리가 공부하고 있는 Visual C++는 C++ 프로그램뿐 아니라 C 프로그램의
56

     개발도 가능하므로 Windows API를 사용하는 애플리케이션을 당연히 개발할 수 있다.

     일반적으로 API라는 용어는 특정 시스템의 기능을 사용하기 위해 제공되는 함수나 모듈을
     뜻한다. 따라서 Windows API는 윈도우 운영체제의 기능을 애플리케이션 프로그램에서
     사용하기 위한 함수의 집합으로 이해하면 될 것이다.

     API를 사용하면 애플리케이션 입장에서 필수적으로 구현해야 하는 여러 가지 기능을 별다
     른 노력없이 사용할 수 있어 개발 기간을 단축할 수 있는 장점이 있고, 표준화된 인터페이
     스를 제공함으로써 좀더 윈도우 애플리케이션답게 만들 수 있게 된다.

     이 외에도 API를 사용해서 얻을 수 있는 효과는 다양한데, 그 중 하나가 운영체제의 버전
     에 독립적인 애플리케이션을 만들 수 있다는 점이다. 현재 사용되고 있는 운영체제로는
     Windows 98, Windows 2000, Windows XP 등이 있는데, 데스크탑에서 사용되는 운영체
     제만 있는 것이 아니라 서버로 사용되는 운영체제도 있다. 또한 각 운영체제의 겉모양은
     비슷할지 모르지만, 내부 로직의 구현은 모두 다르다.

     따라서 만약 어떤 윈도우 애플리케이션을 Windows 98에서만 실행될 수 있도록 개발했다
     면, 즉 Windows 98 운영체제의 내부적인 기능을 직접 다뤄가면서 개발했다면, 이 애플리
     케이션은 Windows 2000이나 XP 등에서는 올바르게 동작한다고 보장할 수 없다. 왜냐하
     면 Windows 98에서는 내부적으로 사용한 기능이 다른 운영체제에서는 지원되지 않거나
     다른 기능으로 변경되었을 수도 있기 때문이다.

     하지만 API를 이용하면 얘기가 달라진다. API는 Windows 98에서도 제공되고 Windows
     2000에서도 제공되기 때문에 API로 주요 기능을 구현했다면 Windows 98에서 실행되는
     애플리케이션은 Windows 2000에서도 정상적으로 실행된다.

     이 경우의 적당한 예로 들 수 있는 것이 바로 파일 열기 대화상자이다. 파일 열기 대화상
     자를 사용하는 애플리케이션을 Windows 98과 Windows XP에서 실행하면 모양이 서로
     다르게 나타난다.

     다음 코드는 파일 열기 대화상자를 표시하는 GetOpenFileName( ) API 함수를 사용한 애
     플리케이션의 소스 중 일부이다.


      case WM_LBUTTONDOWN:
         ZeroMemory(&ofn, sizeof(OPENFILENAME));
         ofn.lStructSize = sizeof(OPENFILENAME);
         ofn.hwndOwner = hWnd;
         ofn.lpstrFile = szFile;
                                                                              57



                      ofn.nMaxFile = sizeof(szFile);
                      ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";
                      ofn.nFilterIndex = 1;
                      ofn.lpstrFileTitle = NULL;
                      ofn.nMaxFileTitle = 0;
                      ofn.lpstrInitialDir = NULL;
                      ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;


                      if (GetOpenFileName(&ofn)!=0)
                         hf = CreateFile(ofn.lpstrFile, GENERIC_READ,
                                      0, (LPSECURITY_ATTRIBUTES) NULL,
                                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                                      (HANDLE) NULL);
                      break;



                   이 소스로 작성된 애플리케이션을 Windows XP에서 실행하면 [그림 4-2]처럼 나타난다.


        그림 4-2
Windows XP에서 실행한
    파일 열기 대화상자




                   이 애플리케이션의 실행 파일을 Windows 98 운영체제에 그대로 복사해서 실행하면 [그림
                   4-3]처럼 나타난다.
58


             그림 4-3
     Windows 98에서 실행한
         파일 열기 대화상자




                        이처럼 API를 이용하면 윈도우에서 제공하는 표준 기능을 통해 필요한 기능을 간단히 구
                        현할 수 있고, 운영체제 플랫폼 간의 독립성도 보장할 수 있는 장점이 있다. 또 상위 운영
                        체제일수록 동일한 함수라도 기능이 향상되어 있는 것을 알 수 있다. 예를 들어 Windows
                        XP의 파일 열기 대화상자에서는 왼쪽에 바탕화면이나 내 문서에 직접 접근할 수 있는 아
                        웃룩(Outlook) 스타일의 버튼을 제공하고, 파일 이름을 입력하기 위한 필드도 목록 형태로
                        제공되는 등 Windows 98에 비해 향상된 기능을 제공한다.

                        하지만 운영체제 간의 호환성이 완벽한 것은 아니다. 특정 API 함수는 운영체제마다 조금
                        씩 다른 기능을 제공하는 경우도 있는데, 이 때는 운영체제 별로 다르게 처리해줘야 한다.
                        따라서 조금이라도 의심되는 API를 사용하게 된다면, 반드시 MSDN 같은 레퍼런스를 통
                        해 호환성을 확인하는 것이 좋다.

                        Windows API는 Win32 API라는 용어로 더 많이 사용되는데, 이것은 가장 많이 사용되는
                        운영체제인 Windows 2000, XP 등이 32비트 운영체제이기 때문이다. Windows 95 이전
                        의 운영체제인 Windows 3.1은 16비트 운영체제였고, 이 때 사용되던 API는 Win16 API라
                        고 한다. 만약 64비트 운영체제가 나온다면 Win64 API라는 용어로 사용될 것이다. 따라
                        서 범용적으로는 Windows API라는 용어를 사용하는 게 좋을 듯하다.
                                                                                           59



                  Windows API 카테고리
                  Windows API는 기능적으로 기본 서비스, 공통 컨트롤, GDI, 네트워크 서비스, 사용자 인
                  터페이스, 윈도우 셸로 나누어 볼 수 있다(표 4-1 참조).


       표 4-1
                  구분                               설명
Windows API의 분류
                                                   컴퓨터의 리소스와 운영체제의 기능을 사용하기 위해 제공되
                  기본 서비스(Base Services)            는 API로, 주로 메모리, 파일 시스템, 프로세스, 쓰레드와 관련
                                                   된 함수가 제공된다.

                  공통 컨트롤(Common Control Library)   운영체제에서 제공되는 공통 컨트롤을 사용하기 위한 API이다.

                                                   화면, 프린터 등 기타 출력장치에 출력을 보내기 위해 필요한
                  GDI(Graphics Device Interface)
                                                   기능을 제공하는 API이다.

                                                   네트워크 상에 존재하는 컴퓨터 간의 통신 기능을 지원하기 위
                  네트워크 서비스(Network Services)
                                                   한 API이다.

                                                   사용자 인터페이스를 구현하기 위해 사용되는 API로, 윈도우의
                  사용자 인터페이스(User Interface)        생성과 데이터의 출력, 사용자로부터의 입력 처리 등에 대한
                                                   기능을 제공한다.

                  윈도우 셸(Windows Shell)             윈도우 셸 기능을 사용하기 위한 API이다.




                  지금까지 Windows API에 대해 개략적으로 살펴보았다. Windows API에 대해 잘 알고 있
                  는 독자라면 쉬운 내용이겠지만, 처음 배우는 독자에게는 조금 어렵게 느껴질 수도 있다.
                  하지만 첫술에 배부를 수는 없는 것이니 이번 장에서는 Windows API가 어떤 것인지 개념
                  만 이해해도 소기의 성과는 거둔 것이라고 하겠다. 다음 장에서 작성하게 되는 예제를 통해
                  실제로 Win32 API 애플리케이션이 어떻게 작성되는지 배우도록 하자.
60
                        첫 번째 Win32 API 애플리케이션

                        4장에서는 Windows API의 개요와 특징, 종류 등에 대해 알아보았다. 이번 장에서는 Visual C++
                        의 Application Wizard를 이용해서 Win32 API 애플리케이션을 제작해본 후, 소스 코드의 분석을

Chapter 05              통해 Win32 API 애플리케이션의 구조를 살펴본다.




                          HelloWorld 애플리케이션
                          Windows API에 대해 배우기 위해 만들 첫 번째 예제는 HelloWorld 애플리케이션이다.




                          프로젝트 생성
                          Visual C++ .NET을 실행하고, [File] - [New] - [Project] 메뉴를 실행한다. 그러면 [그
                          림 5-1]과 같은 대화상자가 나타날 것이다.


            그림 5-1
     New Project 대화상자




                          [그림 5-2]처럼 왼쪽의 Project Types에서 Visual C++ Projects를 선택하고, 오른쪽의
                          Templates에서 Win32 Project를 선택한 다음, 프로젝트를 생성할 Location을 적절히 지
                          정하고 프로젝트명(Name)을 HelloWorld로 설정한다. 설정이 모두 끝나면 [OK] 버튼을 누
                          른다.
                                                                                        61



    그림 5-2
 프로젝트 선택과
    이름 입력




             Win32 프로젝트의 옵션을 설정할 수 있는 Application Wizard(그림 5-3)가 나타나면,
             Application Settings를 선택하고, Application type에서 Windows application을 선택한다
             (기본값으로 선택되어 있을 것이다). 나머지 옵션은 기본값을 그대로 사용한다.


    그림 5-3
프로젝트 옵션 설정




             여기서 [Finish] 버튼을 누르면 프로젝트가 생성된다. 프로젝트의 생성을 완료하면 지정된
             프로젝트 폴더에 소스 파일과 리소스 파일이 자동으로 생성된다.

             [그림 5-4]는 윈도우 탐색기에서 확인한 프로젝트 폴더의 내용이다.
62


         그림 5-4
         HelloWorld
       프로젝트 파일




                      이 책은 Visual C++ .NET을 기준으로 작성되어 있다. 하지만 Visual C++ 6.0도 많이 사
                      용되고 있으므로 함께 설명하겠다(이후 몇 가지 예제는 Visual C++ 6.0으로 프로젝트를 만드는
                      방법을 함께 설명한다).

                      Visual C++ 6.0을 실행하고 [File] - [New] 메뉴를 선택한다. 그러면 프로젝트를 선택하
                      는 대화상자가 나타난다. 여기서 Win32 Application 프로젝트를 선택하고, 프로젝트명과
                      생성 경로를 지정한다(그림 5-5).


         그림 5-5
     Visual C++ 6.0
     프로젝트 생성 -
      New 대화상자
                                                                                          63


                    입력이 끝나면 [OK] 버튼을 누른다. 그러면 [그림 5-6]과 같은 화면이 나타날 것이다.


        그림 5-6
   Visual C++ 6.0
프로젝트 생성 - 1단계




                    여기서 A typical "Hello World" application을 선택하고 [Finish] 버튼을 눌러 프로젝트를
                    생성한다. 프로젝트가 생성된 폴더를 보면 [그림 5-7]처럼 애플리케이션을 작성하기 위한
                    파일들이 생성되어 있는 것을 확인할 수 있다.


        그림 5-7
프로젝트 폴더의 내용




                    이처럼 Application Wizard로 프로젝트를 생성하면, 선택한 애플리케이션을 생성하기 위
                    한 소스 코드가 자동으로 생성된다.
64


     소스 코드 수정
     Class View를 보면 HelloWorld 프로젝트에서 사용하고 있는 함수가 목록 형태로 정리되
     어 있는 것을 볼 수 있고, Solution Explorer를 보면 프로젝트를 구성하고 있는 파일을 볼
     수 있다. 이제 Class View에서 WndProc( ) 함수를 더블클릭해 소스 파일이 에디터에 나
     타나도록 한 다음, WndProc( ) 함수의 중간 부분을 찾아서 다음 코드(굵게 표시된 부분)를
     추가한다.


      LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
      lParam)
      {
          int wmId, wmEvent;
          PAINTSTRUCT ps;
          HDC hdc;


          switch (message)
          {
          case WM_COMMAND:
              wmId     = LOWORD(wParam);
              wmEvent = HIWORD(wParam);
              // Parse the menu selections:
              switch (wmId)
              {
              case IDM_ABOUT:
                  DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
                  break;
              case IDM_EXIT:
                  MessageBox(hWnd, "Hello World! API!", "API", MB_OK);
                  DestroyWindow(hWnd);
                  break;
              default:
                  return DefWindowProc(hWnd, message, wParam, lParam);
              }
              break;
          case WM_PAINT:
              hdc = BeginPaint(hWnd, &ps);
              // TODO: Add any drawing code here...
              EndPaint(hWnd, &ps);
              break;
          case WM_DESTROY:
                                                                                 65



                          PostQuitMessage(0);
                          break;
                      default:
                          return DefWindowProc(hWnd, message, wParam, lParam);
                      }
                      return 0;
                 }



                작성한 코드는 애플리케이션을 종료할 때 메시지 박스가 표시되도록 수정한 것이다. 따라
                서 [File] - [Exit] 메뉴를 실행하여 애플리케이션을 종료하면 메시지 박스가 나타나게 된
                다. 너무 간단한 코드를 예로 든 것 같긴 하지만, 여기서는 Win32 API 애플리케이션의 구
                조를 살펴보기 위한 템플릿 코드의 생성이 주목적이므로 아쉽더라도 이 정도로 만족하도
                록 하자.




                빌드와 실행
                [Build] - [Build Solution] 메뉴를 실행하거나 [F7] 키를 눌러 프로젝트를 빌드해 보자.
                오류가 없다면 정상적으로 컴파일되어 Output 창에 [그림 5-8]과 같은 메시지가 나타날
                것이다.


     그림 5-8
빌드 결과가 나타난
     Output 창




                오류가 발생하면 수정한 후 다시 빌드하도록 하자. 빌드 작업이 완료되면 [Ctrl] - [F5] 키
                를 눌러 애플리케이션을 실행한다. 그러면 [그림 5-9]와 같은 애플리케이션이 실행될 것
                이다.
66


             그림 5-9
             HelloWorld
          애플리케이션을
            실행한 화면




                          [File] - [Exit] 메뉴를 실행하여 애플리케이션을 종료한다. 그러면 WndProc( ) 함수에 추
                          가한 메시지 박스 함수가 실행되어 [그림 5-10]처럼 메시지 박스가 나타날 것이다.


            그림 5-10
     MessageBox( ) 함수가
             실행된 화면




                          메시지 박스의 [확인] 버튼을 누르면 애플리케이션이 종료된다.


                          이상으로 Win32 API 애플리케이션을 생성하고 소스 코드를 작성한 후 빌드를 통해 실행
                          결과까지 확인해 보았다. 계속해서 소스 코드의 분석을 통해 Win32 API 애플리케이션의
                          구조에 대해 파악하도록 하자.




                          Win32 API 애플리케이션의 구조
                          앞에서 Win32 API 애플리케이션을 작성하고 실행해 봤지만, 아직 개념을 이해하지 못한
                          부분이 있을 것이다. 우선 소스를 아무리 찾아봐도 C 언어로 작성되는 프로그램(Win32
                                                                                                  67


                       API 애플리케이션은 C 프로그램이다)에 반드시 있어야 하는 main( ) 함수가 없다. 이외에도
                       자동으로 생성된 소스에 나타나는 함수 중 이해되지 않는 것들이 많을 것이다. 지금부터
                       Win32 API 애플리케이션의 구조를 자세히 알아보도록 하자.




                       WinMain( ) 함수
                       앞에서 작성한 HelloWorld 프로젝트의 Class View를 보면 Global Functions and
                       Variables 항목에 여러 가지 함수가 생성되어 있는 것을 찾아볼 수 있다(그림 5-11).


         그림 5-11
HelloWorld 프로젝트의
          Class View




                       이 함수 중 애플리케이션의 실행시에 가장 먼저 실행되는 함수가 바로 WinMain( ) 함수이
                       다(Visual C++ .NET에서는 _tWinMain( )이고, Visual C++ 6.0에서는 WinMain( )이다). 가장
                       먼저 실행된다는 것은 애플리케이션의 진입점(Entry Point)을 의미하는 것으로, 애플리케
                       이션이 실행되면 이 함수부터 실행된다. 일반적인 C/C++ 프로그램의 진입점이 main( )
                       함수인 것과 마찬가지로 윈도우 애플리케이션의 진입점은 WinMain( ) 함수이며, 다음과
                       같은 원형을 갖는다.


                          int WINAPI WinMain(
                               HINSTANCE hInstance,   // 현재 인스턴스의 핸들
                               HINSTANCE hPrevInstance, // 이전 인스턴스의 핸들
                               LPSTR lpCmdLine,       // 명령 행
                               int nCmdShow           // 초기 상태
                          );



                       첫 번째 인자인 hInstance는 현재 실행되고 있는 애플리케이션 인스턴스의 핸들을 의미하
                       고, 두 번째 인자인 hPrevInstance는 이전에 실행된 애플리케이션 인스턴스의 핸들을 의
                       미한다. 단, 두 번째 인자는 Win32 플랫폼에서는 항상 NULL이 지정된다.
68

                       세 번째 인자인 lpCmdLine은 명령행 인자이고, 네 번째 인자인 nCmdShow는 애플리케이
                       션이 실행될 때 애플리케이션의 형태를 어떻게 보여줄 것인지 지정하는 인자이다.

                       인스턴스(Instance)는 프로그램 코드(파일 형태로 존재하는)가 메모리를 할당받아 실제로 실
                       행되고 있는 개체를 의미한다. 윈도우 운영체제는 동시에 여러 개의 프로그램이 실행될 수
                       있고, 또 한 프로그램이 여러 번 실행될 수도 있기 때문에 실행되고 있는 프로그램을 고유
                       하게 식별할 수 있는 방법이 필요한데, 이를 위해 필요한 것이 인스턴스이다. 프로그램과
                       인스턴스는 다른 개념임을 꼭 기억하도록 하자.

                       WinMain( ) 함수의 세 번째 인자인 lpCmdLine은 main( ) 함수의 argv와 비슷하지만 내
                       용은 조금 다르다. main( ) 함수의 인자는 int argc, char *argv[] 처럼 인자의 개수와 실
                       제값이 배열 형태로 전달되지만, WinMain( ) 함수의 lpCmdLine은 단순한 문자열로 전달
                       되며, argc 같은 인자의 개수는 전달되지 않는다. 따라서 인자의 값을 사용하려면 적절히
                       가공할 필요가 있다.

                       WinMain( ) 함수의 네 번째 인자인 nCmdShow에 지정할 수 있는 값은 [표 5-1]에 정리하
                       였다.


           표 5-1       값                    설명
      WinMain( ) 함수의
     nCmdShow 인자에
                       SW_HIDE              현재 윈도우를 숨기고 다른 윈도우를 활성화한다.
           사용되는 값      SW_MAXIMIZE          윈도우를 최대화한다.

                       SW_MINIMIZE          윈도우를 최소화한다.

                       SW_SHOW              윈도우를 표시한다(크기와 위치는 기본값 지정).

                       SW_SHOWMAXIMIZED     윈도우를 활성화하고 최대화한다. SW_MAXIMIZE와 동일한 값이다.

                       SW_SHOWMINIMIZED     윈도우를 활성화하고 최소화한다.

                       SW_SHOWNORMAL        윈도우를 활성화한다.

                       SW_SHOWMINNOACTIVE   윈도우를 최소화하지만 활성화되지는 않는다.



                       사실 WinMain( ) 함수의 인자는 명령행 인자인 lpCmdLine이나 초기 상태를 지정하는
                       nCmdShow를 제외하면 임의로 지정할 수 있는 값이 아니다. 인스턴스 값을 임의로 지정
                       할 수는 없다. 또 초기 상태를 나타내는 nCmdShow도 애플리케이션을 실행할 때 임의로
                       지정하는 것이 아니라 바탕화면의 바로 가기 속성으로 지정되거나 CreateProcess( ) 함수
                       를 이용하여 애플리케이션을 프로세스 단위로 실행시킬 때 CreateProcess( ) 함수의 인자
                       로 지정할 수 있는 것이다. nCmdShow에 지정되는 값은 실제로는 ShowWindow( ) 함수
                       의 인자로 사용되는 값인데, winuser.h 헤더 파일을 보면 다음과 같이 정의되어 있다.
                                                                              69



 /*
 * ShowWindow() Commands
 */
 #define SW_HIDE                  0
 #define SW_SHOWNORMAL            1
 #define SW_NORMAL                1
 #define SW_SHOWMINIMIZED         2
 #define SW_SHOWMAXIMIZED         3
 #define SW_MAXIMIZE              3
 #define SW_SHOWNOACTIVATE        4
 #define SW_SHOW                  5
 #define SW_MINIMIZE              6
 #define SW_SHOWMINNOACTIVE       7
 #define SW_SHOWNA                8
 #define SW_RESTORE               9
 #define SW_SHOWDEFAULT           10
 #define SW_FORCEMINIMIZE         11
 #define SW_MAX                   11



Class View에서 _tWinMain( ) 함수를 더블클릭해 에디터에 소스가 표시되도록 하자. 다
음은 _tWinMain( ) 함수의 소스이다.


 int APIENTRY _tWinMain(HINSTANCE hInstance,
                            HINSTANCE hPrevInstance,
                            LPTSTR     lpCmdLine,
                            int        nCmdShow)
 {
      // TODO: Place code here.
      MSG msg;
      HACCEL hAccelTable;


      // Initialize global strings
      LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
      LoadString(hInstance, IDC_HELLOWORLD, szWindowClass, MAX_LOADSTRING);
      MyRegisterClass(hInstance);


      // Perform application initialization:
      if (!InitInstance (hInstance, nCmdShow))
      {
70


               return FALSE;
           }


           hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_HELLOWORLD);


           // Main message loop:
           while (GetMessage(&msg, NULL, 0, 0))
           {
               if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
               {
                   TranslateMessage(&msg);
                   DispatchMessage(&msg);
               }
           }


           return (int) msg.wParam;
      }



     이 소스가 하는 일을 간단히 설명하면 다음과 같다. WinMain( ) 함수는 애플리케이션을
     초기화하고 메인 윈도우를 표시하며, 메시지 루프를 실행시켜 애플리케이션으로 전달되는
     메시지를 처리하는 작업을 수행한다. 각 기능은 해당 기능을 수행하기 위한 함수와 문장의
     집합으로 이루어져 있다. main( ) 함수와 비교해 더 다양한 작업을 수행한다는 것을 알 수
     있다.

     소스 중에서 LoadString( )이나 LoadAccelerators( ) 함수의 인자로 사용되는 상수는 애
     플리케이션에서 사용되는 리소스를 나타내는 것으로, Resource View를 보면 리소스 종류
     별로 구분되어 나타나는 것을 확인할 수 있다.

     만약 WinMain( ) 함수의 네 번째 인자인 nCmdShow의 값을 확인해보고 싶다면 다음과
     같은 코드를 메시지 루프 전에 추가하고 애플리케이션을 빌드한 후 실행하면 된다.


           switch(nCmdShow)
           {
           case SW_HIDE:
               MessageBox(NULL,"SW_HIDE", "nCmdShow", MB_OK);
               break
           case SW_MINIMIZE:
               MessageBox(NULL,"SW_MINIMIZE", "nCmdShow", MB_OK);
                                                                   71



       break
   case SW_MAXIMIZE:
       MessageBox(NULL,"SW_MAXIMIZE", "nCmdShow", MB_OK);
       break
   case SW_SHOW:
       MessageBox(NULL,"SW_SHOW", "nCmdShow", MB_OK);
       break
   case SW_SHOWMINIMIZED:
       MessageBox(NULL,"SW_SHOWMINIMIZED", "nCmdShow", MB_OK);
       break
   case SW_SHOWNORMAL:
       MessageBox(NULL,"SW_SHOWNORMAL", "nCmdShow", MB_OK);
       break
   case SW_SHOWMINNOACTIVE:
       MessageBox(NULL,"SW_SHOWMINNOACTIVE", "nCmdShow", MB_OK);
       break
   }


   // Main message loop:
   while (GetMessage(&msg, NULL, 0, 0))
   {
       if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
       {
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
   }



이 코드를 추가하고 애플리케이션을 실행하면 실행할 때마다 상황에 맞는 메시지 박스가
나타날 것이다. 애플리케이션의 실행 파일을 윈도우 바탕화면에 바로 가기로 만든 후, 등
록정보의 실행 옵션을 바꿔가면서 테스트 해보면 nCmdShow에 어떤 값이 지정되는지 알
수 있을 것이다. [그림 5-12]는 바로 가기(단축 아이콘)의 등록정보에서 ‘실행’ 옵션을 변경
하고 있는 화면이다.
72


      그림 5-12
     실행 옵션 변경




                MyRegisterClass( ) 함수
                MyRegisterClass( ) 함수는 생성될 윈도우가 가질 속성을 WNDCLASSEX 구조체의 멤버
                를 통해 지정하고, RegisterClassEx( ) 함수를 이용해 등록한다. 단, WND CLASSEX 구
                조체의 이름에 CLASS가 포함되어 있으므로 객체지향 개념에서 말하는 클래스로 생각하기
                쉬운데, 이 클래스와는 무관하다. WNDCLASSEX 구조체는 다음과 같은 멤버로 구성되어
                있다.


                  typedef struct _WNDCLASSEX {
                      UINT      cbSize;          // WNDCLASSEX 구조체의 크기
                      UINT      style;           // 윈도우 스타일
                      WNDPROC   lpfnWndProc;    // 윈도우 프로시저의 포인터
                      int       cbClsExtra;     // 예약 공간
                      int       cbWndExtra;     // 예약 공간
                      HINSTANCE hInstance;      // 인스턴스 핸들
                      HICON     hIcon;           // 아이콘
                      HCURSOR   hCursor;         // 커서
                      HBRUSH    hbrBackground; // 윈도우 배경
                      LPCTSTR   lpszMenuName; // 메뉴
                      LPCTSTR   lpszClassName; // 윈도우 클래스 이름
                      HICON     hIconSm;         // 아이콘(작은 크기)
                  } WNDCLASSEX, *PWNDCLASSEX;
                                                                           73


멤버를 보면 알 수 있겠지만, 이 구조체는 애플리케이션 윈도우에서 사용하는 아이콘
(hIcon), 커서(hCursor), 메뉴(lpszMenuName), 윈도우 프로시저(lpfnWndProc) 등을 지정하
는 역할을 한다. 이 후에 RegisterClassEx( ) 함수를 이용하여 등록하면, 앞으로 생성되는
윈도우는 지정한 아이콘, 커서, 메뉴를 갖게 된다. 이 때 사용되는 아이콘, 커서 등은 리소
스로 등록되어 있으며, Resource View를 보면 확인할 수 있다.

다음은 MyRegisterClass( ) 함수의 소스이다.


 ATOM MyRegisterClass(HINSTANCE hInstance)
 {
     WNDCLASSEX wcex;


     wcex.cbSize = sizeof(WNDCLASSEX);


     wcex.style          = CS_HREDRAW | CS_VREDRAW;
     wcex.lpfnWndProc    = (WNDPROC)WndProc;
     wcex.cbClsExtra     = 0;
     wcex.cbWndExtra     = 0;
     wcex.hInstance      = hInstance;
     wcex.hIcon          = LoadIcon(hInstance, (LPCTSTR)IDI_HELLOWORLD);
     wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
     wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
     wcex.lpszMenuName   = (LPCTSTR)IDC_HELLOWORLD;
     wcex.lpszClassName = szWindowClass;
     wcex.hIconSm        = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);


     return RegisterClassEx(&wcex);
 }



이 소스를 보면, 앞에서 설명한 것처럼 WNDCLASS 구조체를 이용해서 윈도우의 속성을
지정하고, RegisterClass( ) 함수를 이용해서 등록하는 코드를 확인할 수 있다.

WNDCLASSEX 구조체의 멤버에 대한 자세한 설명은 [표 5-2]에 정리하였다.
74


       표 5-2      멤버                설명
     WNDCLASSEX
                  cbSize            WNDCLASSEX 구조체의 크기가 지정되므로, sizeof 연산자를 이용해 크기를 계산한
     구조체의 멤버
                                    것이다.

                  style             CS_HREDRAW | CS_VREDRAW는 윈도우의 크기가 변경되면 전체 윈도우를 갱신하
                                    라는 의미이다.

                  cbClsExtra
                                    일반적인 경우 0으로 지정한다.
                  cbWndExtra

                  hInstance         함수의 인자로 전달된 인스턴스를 지정한다. 이 인자는 결국 WinMain( ) 함수의 인자
                                    이다.

                  hIcon             애플리케이션 윈도우에서 사용할 아이콘을 지정한다.

                  hCursor           애플리케이션 윈도우에서 사용할 커서를 지정한다. IDC_ARROW는 윈도우 표준 커서
                                    이다.

                                    애플리케이션 윈도우가 갖게 되는 배경을 지정한다. 배경은 브러시(Brush) 형태로 지
                  hbrBackground
                                    정되며, 이 책에서 그래픽 부분을 학습할 때 자세히 설명한다.

                  lpszMenuName      메뉴 이름을 지정한다.

                  lpszClassName     윈도우 클래스 이름을 지정한다.

                  hIconSm           작은 크기의 아이콘을 지정한다. 윈도우 탐색기에서 ‘간단히’ 또는 ‘자세히 보기’를 선
                                    택했을 때 나타나는 아이콘이다.




                  InitInstance( ) 함수
                  InitInstance( ) 함수는 애플리케이션 윈도우를 생성하고 화면에 표시하는 기능을 수행한
                  다. 다음은 InitInstance( ) 함수의 소스이다.


                   BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
                   {
                          HWND hWnd;


                          hInst = hInstance; // Store instance handle in our global variable


                          hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
                              CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);


                          if (!hWnd)
                          {
                              return FALSE;
                                                                                    75



                         }


                         ShowWindow(hWnd, nCmdShow);
                         UpdateWindow(hWnd);


                         return TRUE;
                    }



                   우선 윈도우를 생성하기 위해 CreateWindow( ) 함수를 호출한다. CreateWindow( ) 함수
                   의 원형은 다음과 같다.


                        HWND CreateWindow(
                             LPCTSTR lpClassName, // 윈도우 클래스 이름
                             LPCTSTR lpWindowName, // 윈도우 이름
                             DWORD dwStyle,        // 윈도우 스타일
                             int x,               // 윈도우의 X 좌표
                             int y,               // 윈도우의 Y 좌표
                             int nWidth,          // 윈도우의 가로 길이
                             int nHeight,         // 윈도우의 높이
                             HWND hWndParent,     // 부모 윈도우 핸들
                             HMENU hMenu,         // 메뉴 핸들
                             HINSTANCE hInstance, // 인스턴스 핸들
                             LPVOID lpParam       // 윈도우 생성 데이터
                        );



                   CreateWindow( ) 함수의 첫 번째와 두 번째 인자로 사용된 szWindowClass, szTitle은
                   WinMain( ) 함수에서 LoadString( ) 함수를 사용하여 리소스에서 적재된 문자열 변수로,
                   각각 윈도우의 클래스명과 타이틀명을 나타낸다. dwStyle은 생성되는 윈도우의 스타일을
                   지정하기 위한 인자로 [표 5-3]과 같은 값이 지정된다.


        표 5-3      값                            설명
dwStyle 인자에 지정할
                   WS_BORDER                    얇은(thin) 경계를 갖는 윈도우를 생성한다.
          수 있는 값
                   WS_CAPTION                   타이틀 바를 갖는 윈도우를 생성한다.

                   WS_CHILD                     차일드 윈도우를 생성한다.

                   WS_HSCROLL                   수평 스크롤바를 갖는 윈도우를 생성한다.

                   WS_MAXIMIZE                  초기 실행시 최대화되는 윈도우를 생성한다.
76


     값                     설명

     WS_MAXIMIZEBOX        최대화 버튼을 갖는 윈도우를 생성한다.

     WS_MINIMIZE           초기 실행시 최소화되는 윈도우를 생성한다.

     WS_MINIMIZEBOX        최소화 버튼을 갖는 윈도우를 생성한다.

     WS_OVERLAPPED         중첩 윈도우를 생성한다(타이틀 바와 윈도우 경계를 갖는 윈도우).

                           WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICK
     WS_OVERLAPPEDWINDOW   FRAME, WS_MINIMIZE, WS_MAXIMIZE 속성을 갖는 윈도우를
                           생성한다.

     WS_SYSMENU            시스템 메뉴를 갖는 윈도우를 생성한다.

     WS_THICKFRAME         크기를 조절할 수 있는 경계를 갖는 윈도우를 생성한다.

     WS_VISIBLE            윈도우 생성시 보이는 윈도우를 생성한다.

     WS_VSCROLL            수직 스크롤바를 갖는 윈도우를 생성한다.



     InitInstance( ) 함수에서 호출된 CreateWindow( ) 함수의 dwStyle 인자로는 WS_OVER
     LAPPEDWINDOW가 지정되어 있으므로, 타이틀 바, 시스템 메뉴, 조절 가능한 윈도우 경
     계, 최소화/최대화 버튼을 갖는 일반적인 형태의 윈도우가 생성되리라는 것을 짐작할 수
     있다. 이와 같이 윈도우가 정상적으로 생성되면 CreateWindow( ) 함수는 생성된 윈도우의
     핸들을 반환한다.

     CreateWindow( ) 함수까지 실행되었다고 해서 윈도우가 화면상에 나타나는 것은 아니다.
     실제 윈도우의 표시는 ShowWindow( ) 함수에서 이루어진다. 앞에 있는 InitInstance( )
     함수의 소스를 보면 ShowWindow( ) 함수의 인자로 CreateWindow( ) 함수의 반환값인 윈
     도우 핸들을 지정하는 것을 확인할 수 있다.

     UpdateWindow( ) 함수는 윈도우를 갱신하기 위해 사용된 함수이다. 여기까지 실행되어야
     비로소 애플리케이션이 화면상에 나타나게 되고, 메시지 루프를 통해 애플리케이션으로
     전달되는 메시지를 받아 처리할 수 있게 된다.




     메시지 루프
     윈도우 애플리케이션은 기본적으로 메시지에 의해 실행되는 체제를 갖는다. 메시지에 의
     해 실행된다는 것은 프로그램의 실행 주체가 아니라 외부에서 발생되는 이벤트라는 것을
     의미한다.

     DOS에서 실행되는 프로그램은 프로그램의 실행 순서가 명확하다. 따라서 코드를 작성할
                                                                    77


때 프로그램의 시작 시점부터 종료 시점까지의 흐름을 단순히 함수나 C 언어의 키워드로
기술하면 된다. 하지만 윈도우 애플리케이션은 DOS 프로그램과는 다른 방법으로 실행된
다. 윈도우 애플리케이션은 메뉴나 툴바의 버튼, 마우스 클릭 등에 반응하는 방식으로 실
행되는데, 애플리케이션 입장에서는 사용자가 메뉴를 먼저 선택할지, 툴바의 버튼을 누를
지, 또는 마우스를 클릭할지 알 수 있는 방법이 없다. 따라서 이 이벤트들에 대한 처리를
미리 구현해 놓고, 실제로 이벤트가 발생했을 때 해당 처리를 실행하는 방식을 취한다.

윈도우에서는 이와 같은 이벤트를 메시지(Message)라고 하고, 메시지가 발생했을 때 처리
해야 할 코드를 메시지 핸들러(Message Handler)라고 한다.

윈도우 애플리케이션에서 갖고 있는 메시지 처리 메커니즘이 바로 메시지 루프(Message
Loop)이다. 앞에서 예로 든 HelloWorld 애플리케이션의 소스에는 다음과 같이 작성되어
있다.


      // Main message loop:
      while (GetMessage(&msg, NULL, 0, 0))
      {
          if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
          {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
          }
      }



이 때 사용된 GetMessage( ) 함수는 애플리케이션이 받는 메시지가 저장되어 있는 메시지
큐(Queue)에서 하나의 메시지를 추출하는 역할을 한다. 이 후 이 메시지는 Translate
Message( ) 함수를 거쳐 적절한 상태로 가공되고, DispatchMessage( ) 함수를 거쳐 윈도
우 프로시저로 전송된다.




윈도우 프로시저
메시지 루프를 거쳐 전송된 메시지는 메시지별로 작성되어 있는 처리 루틴을 통해 각각의
작업이 수행된다. 이 메시지 처리 루틴을 메시지 핸들러(Handler)라고 하는데, 메시지 핸
들러는 윈도우 프로시저(Window Procedure)라는 함수 형태로 기술된다. 다음은 앞에서 예
로 든 HelloWorld 프로젝트에서 사용된 윈도우 프로시저이다.
78


     LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
     lParam)
     {
         int wmId, wmEvent;
         PAINTSTRUCT ps;
         HDC hdc;


         switch (message)
         {
         case WM_COMMAND:
             wmId     = LOWORD(wParam);
             wmEvent = HIWORD(wParam);
             // Parse the menu selections:
             switch (wmId)
             {
             case IDM_ABOUT:
                 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
                 break;
             case IDM_EXIT:
                 MessageBox(hWnd, "Hello World! API!", "API", MB_OK);
                 DestroyWindow(hWnd);
                 break;
             default:
                 return DefWindowProc(hWnd, message, wParam, lParam);
             }
             break;
         case WM_PAINT:
             hdc = BeginPaint(hWnd, &ps);
             // TODO: Add any drawing code here...
             EndPaint(hWnd, &ps);
             break;
         case WM_DESTROY:
             PostQuitMessage(0);
             break;
         default:
             return DefWindowProc(hWnd, message, wParam, lParam);
         }
         return 0;
     }
                                                                           79


윈도우 프로시저의 두 번째 인자(message)로 발생한 메시지가 전달되며, 함수 본문에서는
이 값을 switch ~ case 문으로 각각 처리하고 있다. 예를 들어, WM_PAINT 메시지가 발생
하면, 윈도우 프로시저인 WndProc( ) 함수의 message 인자에 WM_PAINT가 전달되고,
switch ~ case 문에 의해 DrawText( ) 함수가 실행된다.

물론 전달되는 메시지에 대한 처리를 보다 세밀하게 구현하려면 메시지를 변형하는 과정
을 거쳐야 하는데, 이것은 12장에서 자세히 설명한다.

여기서 독자는 아마도 한 가지 궁금증이 생겼을 것이다. 메시지 루프를 통해 메시지가 가
공되고 윈도우 프로시저로 전달되어 해당 처리를 수행한다는 것은 알겠는데, 윈도우 프로
시저로 사용되는 함수가 WndProc( ) 함수인지 어떻게 알 수 있는 것일까? 즉, 특정 메시
지가 발생했을 때 어떻게 발생된 메시지를 WndProc( ) 함수로 보낼 수 있는 것일까? 이것
은 윈도우 클래스를 등록하는 MyRegisterClass( ) 함수를 보면 해답을 찾을 수 있다. 윈도
우 클래스를 등록하기 위해 사용된 구조체 WNDCLASSEX의 멤버를 지정하는 코드를 보
면 다음과 같은 부분(굵게 표시된 부분)이 있는 것을 알 수 있다.


 ATOM MyRegisterClass(HINSTANCE hInstance)
 {
     WNDCLASSEX wcex;


     wcex.cbSize = sizeof(WNDCLASSEX);


     wcex.style          = CS_HREDRAW | CS_VREDRAW;
     wcex.lpfnWndProc    = (WNDPROC)WndProc;
     wcex.cbClsExtra     = 0;
     wcex.cbWndExtra     = 0;
     wcex.hInstance      = hInstance;
     wcex.hIcon          = LoadIcon(hInstance, (LPCTSTR)IDI_HELLOWORLD);
     wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
     wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
     wcex.lpszMenuName   = (LPCTSTR)IDC_HELLOWORLD;
     wcex.lpszClassName = szWindowClass;
     wcex.hIconSm        = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);


     return RegisterClassEx(&wcex);
 }
80

     소스를 보면, WNDCLASSEX 구조체의 lpfnWndProc 멤버에 윈도우가 사용하게 될 프로
     시저를 지정하고 있는 것을 볼 수 있다. 함수명에 괄호를 사용하지 않으면 함수의 주소를
     의미한다. 따라서 이 코드는 윈도우 프로시저 WndProc( ) 함수의 주소를 lpfnWndProc 멤
     버에 지정한다.

     그리고 WNDCLASSEX 구조체의 hInstance 멤버에는 MyRegisterClass( ) 함수의 인자로
     전달된 hInstance가 지정되고 있는 것을 볼 수 있는데, 이것은 WinMain( ) 함수의 인자로
     전달된 hInstance이며, 최종적으로 CreateWindow( ) 함수의 인자로 전달되어 윈도우를
     생성하기 위한 조건으로 사용된다. 또한 lpszClassName 멤버에 지정되는 윈도우 클래스
     이름도 CreateWindow( ) 함수의 첫 번째 인자로 사용된다. 이처럼 윈도우를 구동시키기
     위한 각각의 기능이 hInstance라는 인스턴스 핸들과 클래스 이름으로 연결되어 있으므로
     메시지가 발생했을 때, 메시지를 처리할 윈도우 프로시저를 찾아갈 수 있는 것이다.

     만약 새로운 윈도우 프로시저를 WndProc2라는 이름으로 작성하고, WNDCLASSEX의
     lpfnWndProc 멤버의 값을 WndProc2로 변경해 주면, 새로 작성한 WndProc2 함수를 윈
     도우 프로시저로 사용할 수 있다(물론 이와 같이 처리하는 경우는 거의 없다. 단지 예를 들기 위
     해 설명한 것이다).

     정리하면, Win32 API 애플리케이션은 WinMain( ) 함수로부터 시작되며, 윈도우의 속성
     을 정의하는 윈도우 클래스를 통해 사용할 윈도우를 등록한 다음, 윈도우를 생성하고 화면
     에 나타나도록 처리하며, 이 후 윈도우에서 발생하는 메시지를 처리하는 과정을 반복하면
     서 실행된다.




     지금까지 Win32 API 애플리케이션의 구조를 살펴보았다. 물론 더 자세히 설명되어야 할
     부분도 있지만, 이 책의 범주를 넘어서는 내용이므로 더 이상 설명하지는 않겠다. Win32
     API에 대한 자세한 내용을 알고 싶은 독자는 API를 전문적으로 다루고 있는 서적을 참고
     하기 바란다.
                                                                                        81
                MFC 애플리케이션 개요

                4장과 5장에서 Win32 API를 이용해 윈도우 애플리케이션을 개발하는 방법을 알아보았다. 6장에
                서는 윈도우 애플리케이션을 개발하는 또 다른 방법인 MFC에 대해 알아본다.

Chapter 06


                 MFC의 개요
                 MFC(Microsoft Foundation Class Library)는 Microsoft사에서 제공하는 윈도우 애플리케이
                 션 개발용 클래스 라이브러리이다. 클래스(Class)는 데이터와 데이터를 처리하기 위한 함
                 수로 표현되는 자료형을 의미한다. 그렇다면 MFC 클래스는 어떤 데이터와 함수로 구성되
                 어 있을까? 그 답 중의 하나가 바로 5장에서 학습한 Win32 API 함수이다.

                 Win32 API는 운영체제의 다양한 기능을 사용하기 위한 많은 함수를 제공한다. 하지만 이
                 함수들은 단순한 전역함수이기 때문에 함수 간의 관계(이론적인 관계가 아니라 시스템적으로
                 검증할 수 있는 관계)가 존재하지 않으며, 모든 함수는 수평적이라고 볼 수 있다.

                 MFC는 이러한 API 함수를 함수의 특징 또는 특성에 따라 그룹(클래스)으로 분류하고, 관
                 련된 API 함수(멤버함수)를 그룹의 멤버로 포함시킨 다음, 각 API 함수에서 주로 사용되는
                 속성(멤버변수)을 추출하여 그룹의 멤버로 구성한 클래스 라이브러리이다. 또한 클래스는
                 상속을 통해 체계화된 구조를 만들 수 있으므로, MFC 클래스로 재구성된 API 함수들도
                 체계적인 구조를 갖게 된다.

                 그렇다고 해서 MFC가 API 함수로만 구성되는 것은 아니다. API 함수를 클래스로 작성한
                 라이브러리 외에도 윈도우 애플리케이션의 개발에 필요한 다양한 기능이 클래스, 전역함
                 수, 전역 변수, 또는 매크로의 형태로 제공된다. 이 때 MFC에서 제공되는 전역함수는 Afx
                 로 시작하는 이름을 갖게 된다.

                 지금까지 설명한 MFC의 구조를 그림으로 표현한다면, [그림 6-1]과 같다.

      그림 6-1
                                           MFC Library
MFC 라이브러리의 구조

                    MFC Class

                                                                 전역 함수/변수
                     API → MFC Class     기타 MFC Class
                                                                 매크로
82


               MFC 라이브러리의 구성
               MFC 라이브러리를 구조상으로 분류하면, 최상위 클래스인 CObject 클래스로부터 파생되
               는 클래스와 그렇지 않은 클래스로 구분할 수 있다. 일반적으로 윈도우 애플리케이션의 기
               능 구현에 관련된 클래스들이 CObject 클래스로부터 파생되어 정의되고, 독립적으로 구분
               할 수 있는 기능들이 별도의 그룹을 이루면서 클래스로 정의된다.


     그림 6-2
     MFC 계층도
               CObject




                         애플리케이션 아키텍처 클래스                           비 CObject 파생 클래스
                         - CCmdTarget, CWinThread, CDocTemplate…
                                                                   ․   인터넷 서버 API 클래스
                                                                   ․   런-타임 객체 모델 지원 클래스
                         예외 처리 클래스                                 ․   구조 클래스
                         - CException…                             ․   자료 형(Value Type) 클래스
                                                                   ․   지원 클래스
                                                                   ․   형식 템플릿 컬렉션 클래스
                         파일 서비스 클래스                                ․   OLE 타입 래퍼(Wrapper) 클래스
                         - CFile, CMemFile, CStdioFile…            ․   OLE 자동화 클래스
                                                                   ․   동기화 클래스

                         그래픽 처리 클래스
                         컨트롤 지원 클래스
                         그래픽 처리 객체 클래스
                         메뉴 클래스
                         명령행 클래스
                         ODBC 데이터베이스 지원 클래스
                         DAO 데이터베이스 지원 클래스
                         동기화 클래스
                         윈도우 소켓 클래스
                         배열 클래스
                         리스트 클래스
                         맵(Map) 클래스
                         인터넷 서비스 클래스


                         윈도우 지원 클래스
                          ․   프레임 윈도우 클래스
                          ․   컨트롤 바 클래스
                          ․   속성 시트 클래스
                          ․   대화상자 클래스
                          ․   뷰 클래스
                          ․   컨트롤 클래스
                                                                   83



MFC 계층도
MFC는 많은 수의 클래스로 구성되기 때문에 이 책에서 모든 클래스를 소개하기는 어렵
다. MFC 클래스의 상속관계(계층도)를 위주로 [그림 6-2]처럼 표현할 수 있을 것이다.




MFC 클래스로 포장된 API 함수
MFC는 API 함수를 객체지향 개념을 적용하여 클래스로 포장(보통 Wrapping이라는 용어를
사용하는데, 굳이 번역하자면 둘러싼다는 의미로 볼 수 있겠다)한 라이브러리이다. 따라서 MFC
클래스의 멤버함수로 정의되는 API 함수는 대부분의 함수 이름이 API 함수와 동일하다는
특징을 갖는다. API 함수 중 하나인 TextOut() 함수의 경우를 예로 들어 보자. TextOut()
함수는 화면에 문자를 출력하는 함수로, MFC 클래스인 CDC 클래스의 멤버함수로 정의되
어 있다(CDC 클래스와 TextOut( ) 함수에 대한 설명은 9장에서 볼 수 있다). 다음은 TextOut( )
API 함수의 원형이다.


  BOOL TextOut(
       HDC hdc,             // DC 핸들
       int nXStart,         // 출력될 문자열의 X 좌표
       int nYStart,         // 출력될 문자열의 Y 좌표
       LPCTSTR lpString, // 문자열
       int cbString         // 문자열의 개수
  );



다음은 CDC 클래스의 멤버함수인 TextOut( ) 함수의 정의이다.


  virtual BOOL TextOut(
       int x,
       int y,
       LPCTSTR lpszString,
       int nCount
  );


  BOOL TextOut(
       int x,
       int y,
       const CString& str
  );
84

     두 가지를 비교해보면, 함수의 인자와 반환형만 다르고 함수의 이름은 동일하다는 것을 알
     수 있다. 사실 API 함수를 MFC 클래스로 정의하면서 반드시 함수 이름을 동일하게 할 필
     요는 없다. 하지만 API를 사용하던 개발자의 편의와 일관성을 위해 큰 문제가 없는 한
     MFC에서도 동일한 이름을 사용하고 있다.

     계속해서 두 가지 TextOut( ) 함수(API, MFC)의 인자를 살펴보자. API 함수로 사용되는
     TextOut( ) 함수의 첫 번째 인자로 문자가 출력될 윈도우의 핸들이 지정되고, 나머지 인자
     로 출력될 문자열과 위치가 지정된다. 이에 비해 CDC 클래스의 멤버함수인 TextOut( ) 함
     수는 윈도우 핸들을 지정하는 인자는 없고, 문자열과 위치를 지정하는 인자만 존재한다. 이
     는 CDC 클래스의 객체가 생성될 때 이미 문자열을 출력할 대상 윈도우에 대해 생성되기 때
     문이며, 출력 대상이 되는 윈도우를 따로 지정할 필요가 없다. 이에 비해 API 함수로서의
     TextOut( ) 함수는 독립적인 전역함수이기 때문에 문자열을 출력할 윈도우를 명시적으로
     지정해줘야 한다.

     이와 함께 CDC 클래스의 TextOut( ) 함수의 인자 구성을 보면 다른 또 하나의 TextOut( )
     함수가 있는 것을 볼 수 있는데, 이것은 객체지향 개념의 특징인 ‘중복정의’를 이용하여 또
     하나의 TextOut( ) 함수를 정의한 것이다. 두 번째 TextOut( ) 함수는 출력할 문자열의 위
     치를 지정하는 인자만 변경된 것을 볼 수 있다. 따라서 상황에 따라 적당한 함수를 골라서
     사용하면 될 것이다. API 함수를 기반으로 하는 MFC 클래스의 멤버함수는 대부분 이와
     같은 체제를 갖고 있으므로, 특징을 잘 이해하면 보다 편리하게 MFC 클래스를 사용할 수
     있다.



           중복정의
     동일한 이름으로 여러 개의 함수를 정의할 수 있는 객체지향 프로그래밍의 특징이다. 함수의 인자
     개수와 자료형이 다르면, 이름은 동일하지만 별개의 함수로 간주된다. 단, 함수의 반환형만 다를 경
     우에는 중복정의가 되지 않는다.




     기타 MFC 클래스
     MFC 라이브러리는 위에서 설명한 클래스 외에 API 함수에는 존재하지 않지만 윈도우 애
     플리케이션 개발에 유용한 기능을 정의하고 있는 클래스도 함께 제공한다. 예를 들면, 데
     이터베이스 애플리케이션을 개발하기 위한 DAO(Database Access Objects)나 ODBC(Open
     Database Connectivity) 관련 기능을 멤버함수로 구현한 클래스를 제공하며, 네트워크 프로
     그래밍에 사용되는 윈도우 소켓 함수, 인터넷 관련 기능을 지원하기 위한 인터넷 서비스,
                                                                85


복잡한 형태의 자료형을 처리하기 위한 클래스 등 윈도우 애플리케이션 개발에 필요한 많
은 기능을 클래스로 제공한다. 종류가 매우 많으므로 이후 필요한 기능이 나올 때마다 설
명하도록 하겠다.




전역함수
MFC 라이브러리는 클래스 라이브러리이지만 클래스만 존재하는 것은 아니며, 애플리케이
션 개발에 편리하게 사용할 수 있는 여러 가지 기능이 전역 함수로 정의되어 있다. 이런
함수는 함수 이름 앞에 Afx라는 접두어가 붙여진다. 예를 들어 메시지 박스를 나타내는
API 함수인 MessageBox( ) 함수는 MFC 전역 함수에 다음과 같이 정의되어 있다.


  int AfxMessageBox(
       LPCTSTR lpszText,
       UINT nType = MB_OK,
       UINT nIDHelp = 0
  );


  int AFXAPI AfxMessageBox(
       UINT nIDPrompt,
       UINT nType = MB_OK,
       UINT nIDHelp = (UINT) -1
  );



AfxMessageBox( ) 함수는 원래의 API 함수인 MessageBox( ) 함수과 비교했을 때 기능과
인자 구성에 별다른 차이가 없다. 따라서 MFC 애플리케이션에서 AfxMessageBox( ) 함수
대신 MessageBox( ) 함수를 사용해도 문제가 되지는 않지만, 가급적이면 MFC에서 제공
하는 전역 함수를 사용하는 것이 좋다.




MFC Framework
윈도우 애플리케이션의 개발에 MFC를 이용할 때 얻는 가장 큰 장점은 MFC Framework
를 사용할 수 있다는 것이다. MFC Framework란 애플리케이션의 구조를 기능별로 분할
하고, 분할된 기능을 하나의 클래스가 전담해서 처리하도록 구현한 체제를 말한다. 윈도우
운영체제에서 실행되는 애플리케이션은 일반적으로 [그림 6-3]과 같이 구성되어 있다.
86


          그림 6-3
         윈도우 애플리                                                     Frame Window
         케이션의 구조




                                                                     View(Client 영역)




                                      Document




                    [그림 6-3]을 보면 애플리케이션을 Frame Window 영역, View(Client) 영역, 그리고
                    Document 영역으로 구분할 수 있다는 것을 알 수 있다. Frame Window 영역은 윈도우의
                    타이틀 바, 메뉴, 윈도우 경계 등을 포함하는 영역이고, View 영역은 애플리케이션에서
                    출력하는 데이터가 표시되는 영역이다. Document는 번역하면 문서라는 뜻이 되지만,
                    MFC에서의 Document는 데이터라는 의미로 보는 것이 좋으며, 화면 외부로 보이는 부분
                    이 아니기 때문에 그림처럼 표현했다.

                    그러면 이렇게 분할된 애플리케이션 영역을 MFC가 어떻게 관리하는지 살펴보도록 하자.
                    [표 6-1]은 각 영역을 관리하기 위한 MFC 클래스를 정리한 것이다.


           표 6-1
                    클래스         설명
     애플리케이션의 영역을
     담당하는 MFC 클래스   CFrameWnd   Frame Window 영역 - 애플리케이션의 외부 프레임과 관련된 기능(메뉴, 툴바 등)을 제어
                                하기 위한 함수가 정의되어 있다.

                    CView       View 영역(클라이언트 영역) - 화면 출력에 대한 기능이 정의되어 있다.

                    CDocument   Document 영역 - 애플리케이션에서 사용되는 주요 데이터가 정의된다.



                    결국 MFC 애플리케이션은 각 클래스의 객체가 유기적으로 결합되어 실행되는 체제로 볼
                    수 있다. 그렇다면 어떤 원리에 의해 객체들이 데이터를 주고받으면서 하나의 애플리케이
                    션으로 실행될 수 있을까? 해답은 클래스의 객체를 하나로 묶어주는 또 하나의 클래스에
                    있는데, 바로 CWinApp 클래스이다.
                                                                                     87


           CWinApp 클래스는 애플리케이션 클래스라고도 하는데, 애플리케이션에서 사용하는
           Document 객체와 View 객체를 하나의 그릇(Template)에 담는 역할을 한다. 따라서 하나
           의 그릇에 담겨있는 객체 간에는 서로 정보를 확인할 수 있게 된다. CWinApp 클래스는
           이와 함께 애플리케이션의 초기화와 종료에 대한 처리를 수행한다. 이에 대한 자세한 내용
           은 7장에서 MFC 애플리케이션을 개발할 때 관련 소스를 보면서 다시 설명하겠다.

           [그림 6-4]는 CWinApp 클래스를 고려해 다시 그려본 MFC 애플리케이션의 구조이다.


  그림 6-4
 윈도우 애플리
케이션의 구조와                                           Frame Window      → CFrameWnd 클래스
  관련 클래스




                                                   View(Client 영역)     → CView 클래스




                         +                         CWinApp 클래스
                      Document   → CDocument 클래스




           지금까지 MFC 애플리케이션 개요에 대해 알아보았다. 다음 장에서는 이론적으로 살펴본
           내용을 실제 MFC 애플리케이션의 개발을 통해 더 자세히 알아볼 것이다.
88
                        첫 번째 MFC 애플리케이션
                        6장에서 MFC의 개요와 특징에 대해 알아보았다. 이번 장에서는 5장과 동일한 방법으로 실제
                        MFC 애플리케이션을 만들어 본 후, 생성된 소스코드의 분석을 통해 MFC 애플리케이션의 구조를
                        살펴보도록 하겠다. 그러나 MFC 애플리케이션의 실행 구조는 다소 복잡한 면이 있으므로, 여기
Chapter 07              서는 큰 흐름을 설명하고 세부적인 내용은 이어지는 단원에서 다시 설명하겠다.




                         HelloMFC 애플리케이션
                         우리가 처음으로 만들게 될 MFC 애플리케이션은 HelloMFC이다.




                         프로젝트 생성
                         Visual C++ .NET을 실행하고, [File] - [New] - [Project] 메뉴를 선택한다. 그런 다음,
                         Project Types에서 Visual C++ Projects를 선택하고, Templates은 MFC Application으로
                         선택한 후, 프로젝트명에 HelloMFC라고 입력한다. 프로젝트가 생성될 폴더는 적절히 지정
                         한다. 입력이 완료되면 [OK] 버튼을 누른다.

                         [OK] 버튼을 누르면 MFC Application Wizard가 나타날 것이다. 3장에서 MFC 애플리케
                         이션으로 Application Wizard에 대해 학습했으므로 익숙할 것이다. Application types에
                         서 Single document를 선택하고, Resource language에서는 한국어를 선택한 다음,
                         [Finish] 버튼을 눌러 프로젝트를 생성한다(그림 7-1).


             그림 7-1
     HelloMFC 프로젝트 생성
                                                                                     89


                   [그림 7-2]는 프로젝트가 생성된 폴더를 탐색기로 확인한 화면이다. Win32 API 애플리케
                   이션에 비해 생성된 파일이 더 많은 것을 볼 수 있는데, 이것은 생성되는 클래스 별로 소
                   스 파일과 헤더 파일이 따로 생성되기 때문이다.

        그림 7-2
HelloMFC 프로젝트 파일




                   소스 코드 수정
                   Class View에서 CHelloMFCApp 클래스를 선택한 후, InitInstance( ) 멤버함수를 찾아서
                   함수 이름을 더블클릭한다. 그러면 InitInstance( ) 멤버함수의 소스가 에디터에 나타난다.
                   여기에 다음과 같이 메시지 박스를 표시하는 함수(굵게 표기된 부분)를 추가한다.


                    BOOL CHelloMFCApp::InitInstance()
                    {
                        // 응용 프로그램 매니페스트가 ComCtl32.dll 버전 6 이상을 사용하여 비주얼 스타일을
                        // 사용하도록 지정하는 경우, Windows XP 상에서 반드시 InitCommonControls()가
                        // 필요합니다.
                        // InitCommonControls()를 사용하지 않으면 창을 만들 수 없습니다.
                        InitCommonControls();


                        CWinApp::InitInstance();


                        // OLE 라이브러리를 초기화합니다.
                        if (!AfxOleInit())
90


         {
             AfxMessageBox(IDP_OLE_INIT_FAILED);
             return FALSE;
         }
         AfxEnableControlContainer();
         // 표준 초기화
         // 이들 기능을 사용하지 않고 최종 실행 파일의 크기를 줄이려면
         // 아래에서 필요없는 특정 초기화 루틴을 제거해야 합니다.
         // 해당 설정이 저장된 레지스트리 키를 변경하십시오.
         // TODO: 이 문자열을 회사 또는 조직의 이름 같은
         // 적절한 내용으로 수정해야 합니다.
         SetRegistryKey(_T("로컬 응용 프로그램 마법사에서 생성된 응용 프로그램"));
         LoadStdProfileSettings(4); // MRU를 포함해 표준 INI 파일 옵션을 로드합니다.
         // 응용 프로그램의 문서 템플릿을 등록합니다. 문서 템플릿은
         // 문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.
         CSingleDocTemplate* pDocTemplate;
         pDocTemplate = new CSingleDocTemplate(
             IDR_MAINFRAME,
             RUNTIME_CLASS(CHelloMFCDoc),
             RUNTIME_CLASS(CMainFrame),       // 주 SDI 프레임 창입니다.
             RUNTIME_CLASS(CHelloMFCView));
         AddDocTemplate(pDocTemplate);
         // 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.
         CCommandLineInfo cmdInfo;
         ParseCommandLine(cmdInfo);
         // 명령줄에 지정된 명령을 디스패치합니다. 응용 프로그램이 /RegServer, /Register,
         // /Unregserver 또는 /Unregister로 시작된 경우 FALSE를 반환합니다.
         if (!ProcessShellCommand(cmdInfo))
             return FALSE;
         // 창 하나만 초기화되었으므로 이를 표시하고 업데이트합니다.
         m_pMainWnd->ShowWindow(SW_SHOW);
         m_pMainWnd->UpdateWindow();
         // 접미사가 있을 경우에만 DragAcceptFiles를 호출합니다.
         // SDI 응용 프로그램에서는 ProcessShellCommand 후에 이런 호출이 발생해야 합니다.


         AfxMessageBox("Hello MFC!");


         return TRUE;
     }
                                                                             91


              InitInstance( ) 멤버함수는 애플리케이션이 시작될 때 초기화 작업을 수행한다. 작성한
              코드는 앞에서 만든 Win32 API 애플리케이션과는 반대로 애플리케이션이 시작될 때 메시
              지 박스가 표시되도록 구현하였다. 프로젝트를 생성할 때 언어를 한국어로 지정했기 때문
              에 주석이 한글로 생성된 것을 볼 수 있다.




              빌드와 실행
              빌드 작업까지 완료되면 [Ctrl] - [F5] 키를 눌러 애플리케이션을 실행한다. 그러면 애플리
              케이션이 시작될 때 InitInstance( ) 함수에 기술한 AfxMessageBox( ) 함수가 실행되어
              [그림 7-3]과 같은 메시지 박스가 나타날 것이다.


  그림 7-3
   HelloMFC
애플리케이션을
  실행한 모습




              MFC 애플리케이션을 생성하고 소스 코드를 작성한 후 빌드를 통해 실행 결과까지 확인해
              보았다. 계속해서 소스 코드를 분석하여 MFC 애플리케이션의 구조를 알아보도록 하자.




              MFC 애플리케이션의 구조
              MFC 애플리케이션을 작성하고 실행해 봤는데, Win32 API 애플리케이션과 마찬가지로
              이해하기 어려운 부분이 있다. 심지어 Win32 API 애플리케이션에 있었던 WinMain( ) 함
              수도 존재하지 않는다. 궁금증을 풀기 위해 MFC 애플리케이션의 구조를 좀더 자세히 알
              아보자.
92


     실행 구조
     MFC 애플리케이션의 구조는 Win32 API 애플리케이션과 비교하면 좀더 복잡하다. 물론
     Win32 API 애플리케이션은 순수 C 언어로 작성되고 MFC 애플리케이션은 C++ 언어로
     작성된다는 차이가 있기는 하지만, 이런 이유보다는 MFC Framework 체제가 주요 원인
     이다.

     MFC Framework는 6장에서 설명한 것처럼, 애플리케이션의 구조를 기능별로 분할하고,
     분할된 기능을 해당되는 클래스가 전담해서 처리하는 방식을 취하고 있다. 그리고 이런 작
     업을 처리하는 클래스는 CWinApp, CFrameWnd, CDocument, CView이며, 실제 애플리
     케이션에서는 이 클래스들을 상속받아서 정의된 파생 클래스로 구현한다.

     앞에서 작성한 HelloMFC 프로젝트에 사용된 클래스를 살펴보자.

     Class View에서 CHelloMFCApp 클래스의 이름을 더블클릭 해보자. 그러면 애플리케이션
     클래스인 CHelloMFCApp 클래스가 정의되어 있는 헤더 파일이 열릴 것이다. 다음은
     CHelloMFCApp 클래스의 선언 부분이다.


      class CHelloMFCApp : public CWinApp
      {
      public:
           CHelloMFCApp();



      // 재정의
      public:
           virtual BOOL InitInstance();


      // 구현
           afx_msg void OnAppAbout();
           DECLARE_MESSAGE_MAP()
      };



     CHelloMFCApp 클래스의 상속 관계를 보면, CWinApp 클래스로부터 상속받아 정의되고
     있다. CWinApp 클래스는 6장에서도 설명한 것처럼, Document 클래스와 View 클래스의
     연결, 애플리케이션의 시작과 종료에 대한 처리가 구현되어 있는 클래스이다. 따라서
     CWinApp 클래스를 상속받아서 정의되는 클래스도 당연히 이런 처리가 가능하다.
                                                                    93


다음은 CHelloMFCApp 클래스에 정의되어 있는 InitInstance( ) 멤버함수의 소스이다.


 BOOL CHelloMFCApp::InitInstance()
 {
     // 응용 프로그램 매니페스트가 ComCtl32.dll 버전 6 이상을 사용하여 비주얼 스타일을
     // 사용하도록 지정하는 경우, Windows XP 상에서 반드시 InitCommonControls()가
     // 필요합니다.
     // InitCommonControls()를 사용하지 않으면 창을 만들 수 없습니다.
     InitCommonControls();


     CWinApp::InitInstance();


     // OLE 라이브러리를 초기화합니다.
     if (!AfxOleInit())
     {
         AfxMessageBox(IDP_OLE_INIT_FAILED);
         return FALSE;
     }
     AfxEnableControlContainer();
     // 표준 초기화
     // 이들 기능을 사용하지 않고 최종 실행 파일의 크기를 줄이려면
     // 아래에서 필요 없는 특정 초기화 루틴을 제거해야 합니다.
     // 해당 설정이 저장된 레지스트리 키를 변경하십시오.
     // TODO: 이 문자열을 회사 또는 조직의 이름과 같은
     // 적절한 내용으로 수정해야 합니다.
     SetRegistryKey(_T("로컬 응용 프로그램 마법사에서 생성된 응용 프로그램"));
     LoadStdProfileSettings(4); // MRU를 포함하여 표준 INI 파일 옵션을 로드합니다.
     // 응용 프로그램의 문서 템플릿을 등록합니다. 문서 템플릿은
     // 문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.
     CSingleDocTemplate* pDocTemplate;
     pDocTemplate = new CSingleDocTemplate(
         IDR_MAINFRAME,
         RUNTIME_CLASS(CHelloMFCDoc),
         RUNTIME_CLASS(CMainFrame),       // 주 SDI 프레임 창입니다.
         RUNTIME_CLASS(CHelloMFCView));
     AddDocTemplate(pDocTemplate);
     // 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.
     CCommandLineInfo cmdInfo;
     ParseCommandLine(cmdInfo);
     // 명령줄에 지정된 명령을 디스패치합니다. 응용 프로그램이 /RegServer, /Register,
94


          // /Unregserver 또는 /Unregister로 시작된 경우 FALSE를 반환합니다.
          if (!ProcessShellCommand(cmdInfo))
             return FALSE;
          // 창 하나만 초기화되었으므로 이를 표시하고 업데이트합니다.
          m_pMainWnd->ShowWindow(SW_SHOW);
          m_pMainWnd->UpdateWindow();
          // 접미사가 있을 경우에만 DragAcceptFiles를 호출합니다.
          // SDI 응용 프로그램에서는 ProcessShellCommand 후에 이런 호출이 발생해야 합니다.


          AfxMessageBox("Hello MFC!");


          return TRUE;
      }



     이 소스를 보면 Win32 API 애플리케이션의 InitInstance( ) 함수에서 그랬던 것처럼, 애
     플리케이션의 초기화 작업을 수행하고 있는 것을 볼 수 있다. 여러 가지 내용이 있지만,
     간단히 설명하면 우선 기본 클래스인 CWinApp 클래스의 InitInstance( )와 Afx OleInit( )
     함수, AfxEnableControlContainer( ) 함수를 호출해 필요한 초기화 작업을 수행한 후,
     SetRegistryKey( ) 함수를 이용해서 애플리케이션의 정보를 레지스트리에 기록하게 된다
     (이 정보는 나중에 레지스트리 값을 읽고 쓰기 위한 기준값이 된다).

     이후 CSingleDocTemplate 클래스의 객체 포인터를 이용해서 애플리케이션에서 사용하는
     View 클래스와 Document 클래스, Frame Window 클래스의 정보를 하나로 묶어주고
     (CSingleDocTemplate 클래스의 생성자 이용), AddDocTemplate( ) 함수를 이용해서 애플리
     케이션의 Document Template으로 추가하여 MFC Framework를 구성하는 각 영역이 유
     기적으로 결합될 수 있게 한다.

     CSingleDocTemplate 클래스는 SDI 애플리케이션에서 CDocument 클래스로부터 파생되
     는 Document 클래스, CView 클래스(또는 CScrollView, CFormView, CEditView 등 기타
     View 클래스)로부터 파생되는 View 클래스, 그리고 CFrameWnd 클래스로부터 파생되는
     Frame Window 클래스 간의 관계를 설정하기 위한 클래스이다. 이것은 8장에서 설명되는
     Document/View 아키텍처에서 Document 객체와 View 객체가 서로 인식할 수 있도록 해
     주는 연결점 역할을 하는 것으로 생각할 수 있다.

     다음은 CSingleDocTemplate 클래스의 생성자 함수이다.
                                                                               95



     CSingleDocTemplate(
          UINT nIDResource,
          CRuntimeClass* pDocClass,
          CRuntimeClass* pFrameClass,
          CRuntimeClass* pViewClass
     );



첫 번째 인자로는 애플리케이션에서 사용하는 리소스의 ID가 지정된다. 이 ID는 메뉴, 아
이콘, 액셀러레이터, 툴바를 나타내는 리소스 ID인데, 이 리소스 ID들은 동일한 ID를 사
용한다(Resource View에서 확인해 보기 바란다). 두 번째 인자로는 애플리케이션에서 사용되
는 Document 클래스의 정보가 CRuntimeClass 구조체의 포인터로 지정된다. CRuntime
Class 구조체는 지정된 클래스의 정보를 실행 시간(Runtime) 중에 제공하는 구조체이며,
위 소스에서는 RUNTIME_CLASS 매크로를 이용해 CRuntimeClass 구조체의 포인터를
구하고 있다. 세 번째, 네 번째 인자는 두 번째 인자와 동일하며, 대상 클래스가 Frame
Window 클래스, Document 클래스라는 점만 다르다.

만약 SDI 애플리케이션이 아닌 MDI 애플리케이션이라면, CSingleDocTemplate 클래스
대신 CMultiDocTemplate 클래스가 사용되는데, SDI와는 달리 AddDocTemplate( ) 함수
가 Document/View의 쌍만큼 실행된다.

여기까지 초기화 작업이 수행된 후, Win32 API 애플리케이션의 초기화 과정과 비슷하게
ShowWindow( ) 멤버함수와 UpdateWindow( ) 멤버함수를 사용해서 윈도우의 표시와 갱
신 작업을 수행하게 된다.

지금까지 설명한 것이 CHelloMFCApp 클래스의 InitInstance( ) 함수에서 이루어지는 초
기화 작업으로, 애플리케이션 수준의 초기화라고 할 수 있다. 이와는 별도로 MFC
Framework 구조에서 애플리케이션의 외형(프레임 윈도우)을 담당하는 CFrameWnd 클래
스를 기본 클래스로 하는 CMainFrame 클래스도 초기화 코드를 가지고 있다. 다음은
CMainFrame 클래스의 OnCreate( ) 멤버함수의 소스이다.


 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
 {
      if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
            return -1;


      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |
 CBRS_TOP
96


              | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
              !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
          {
              TRACE0("도구 모음을 만들지 못했습니다.\n");
              return -1;     // 만들지 못했습니다.
          }


          if (!m_wndStatusBar.Create(this) ||
              !m_wndStatusBar.SetIndicators(indicators,
               sizeof(indicators)/sizeof(UINT)))
          {
              TRACE0("상태 표시줄을 만들지 못했습니다.\n");
              return -1;     // 만들지 못했습니다.
          }
          // TODO: 도구 모음을 도킹할 수 없게 하려면 이 세 줄을 삭제하십시오.
          m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
          EnableDocking(CBRS_ALIGN_ANY);
          DockControlBar(&m_wndToolBar);


          return 0;
      }



     OnCreate( ) 멤버함수는 프레임 윈도우 영역에 해당하는 툴바와 상태 바에 대한 초기화
     코드가 기술된다. 툴바와 상태 바는 이 책의 2부에서 세부 구현 내용이 다루어지니, 여기
     서는 툴바와 상태 바에 대한 초기화 작업이 이루어진다는 정도만 알아두도록 하자.

     CHelloMFCDoc, CHelloMFCView 클래스는 애플리케이션에서 사용하는 데이터의 관리와
     출력에 관계된 작업을 구현한다. 이 클래스들의 연결 메커니즘은 CHelloMFCApp 클래스
     의 멤버함수인 InitInstance( ) 멤버함수에 구현된 Document Template 등록 부분에서 일
     부 설명되었다. 하지만 이 클래스들의 관계는 좀더 자세히 알아볼 필요가 있기 때문에 ‘8
     장 Document/View 아키텍처’에서 세부 내용을 다시 한 번 다룰 것이다.




     메시지 처리
     Win32 API 애플리케이션에는 윈도우 프로시저라는 함수가 있었다. 이 함수는 애플리케이
     션으로 전송되는 메시지를 받아 해당 처리를 실행한다. 하지만 이 함수는 전역함수이고,
     Win32 API 애플리케이션은 MFC 애플리케이션처럼 기능별로 나누어 처리하는 방식이 아
                                                                   97


니기 때문에, 메시지에 대한 처리가 단순한 화면의 변경에 관한 것이든 데이터의 처리에
관한 것이든 모두 하나의 윈도우 프로시저 함수에서 처리해야 하는 문제가 있다. 물론
switch ~ case 문으로 구분하긴 하지만, 전체적으로 보면 하나의 함수인 것은 분명하다.

하지만 MFC 애플리케이션은 기능별 처리를 담당하는 클래스로 구현되기 때문에 메시지
또한 기능별로 분류하여 처리 로직을 구현할 수 있다. 이 말은 결국 각 클래스에 메시지를
처리하기 위한 윈도우 프로시저 역할을 담당하는 코드가 기술될 수 있다는 뜻이고, 메시지
의 특징에 따라 관련있는 클래스에 메시지 처리를 구현할 수 있다는 뜻이 된다.

다음은 CHelloMFCView 클래스의 소스 파일에서 메시지를 처리하기 위한 코드이다.


 BEGIN_MESSAGE_MAP(CHelloMFCView, CView)
    // 표준 인쇄 명령입니다.
    ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
 END_MESSAGE_MAP()



이 코드는 CHelloMFCDoc 클래스나 CMainFrame 클래스, CHelloMFCApp 클래스에도
동일하게 존재한다. 이것은 메시지가 애플리케이션의 데이터에 관계된 것이면 CHello
MFCDoc 클래스에 메시지 처리 로직이 기술되고, 단순한 화면 출력에 대한 메시지면
CHelloMFCView 클래스에 메시지 처리 로직이 기술되며, 애플리케이션의 실행에 관련된
것이면 CHelloMFCApp 클래스에 기술된다는 것을 뜻한다.

언뜻 보면 코드의 내용이 잘 이해되지 않을 수도 있다. 사실 위의 코드는 함수 호출처럼
보이지만, 함수가 아닌 매크로 정의이다(문장 끝에 세미콜론이 없다는 것에 주목하자). 메시지
에 대해 설명하려면 꽤 많은 시간이 필요하므로 12장에서 예제와 함께 자세히 설명하기로
하고, 여기서는 메시지를 처리하기 위한 루틴이라는 것만 이해하고 넘어가도록 하자.




MFC 애플리케이션의 실행 원리
Win32 API 애플리케이션이 실행될 때 가장 먼저 실행되는 함수는 진입점(Entry Point)인
WinMain( ) 함수이다. 그리고 DOS 기반의 C/C++ 프로그램은 main( ) 함수가 진입점이
다. 그렇다면 MFC 애플리케이션의 진입점은 어떤 함수일까? 생성된 프로젝트의 소스를
아무리 찾아 봐도 WinMain( ) 함수나 main( ) 함수는 보이지 않을 것이다.
98

     결론부터 말하면 MFC 애플리케이션에서 WinMain( ) 함수는 별도로 작성할 필요가 없다.
     왜냐하면 MFC 라이브러리에 이미 WinMain( ) 함수가 정의되어 있기 때문이다. MFC 애
     플리케이션에서는 WinMain( ) 함수를 별도로 작성하지 않아도 MFC 라이브러리에 정의되
     어 있는 WinMain( ) 함수가 자동으로 링크된다.

     WinMain( ) 함수로부터 애플리케이션이 실행되는 과정은 다음과 같다. 우선 WinMain( )
     함수가 실행되면, 즉 애플리케이션이 시작되면 전역 함수인 AfxWinMain ( ) 함수가 실행된
     다. AfxWinMain( ) 함수는 WinMain( ) 함수를 MFC 버전으로 다시 작성한 것으로, 기능적
     으로는 Win32 API 애플리케이션의 WinMain( ) 함수와 유사하다.

     AfxWinMain( ) 함수는 내부적으로 다시 애플리케이션 클래스(CHelloMFCApp 클래스)에 정
     의되어 있는 InitInstance( ) 멤버함수를 호출하게 된다. 여기서 애플리케이션의 실행에
     필요한 초기화 작업이 이루어지고, 초기화 작업이 끝나면 다시 CWinApp 클래스에 정의되
     어 있는 Run( ) 멤버함수가 실행된다. Run( ) 멤버함수는 메시지 루프 역할을 수행하는 함
     수로, 메시지가 전달되지 않을 때는 OnIdle( ) 멤버함수를 호출하여 Idle 상태의 처리를 담
     당하고, 메시지가 전달되면 TranslateMessage( ), DispatchMessage( ) 함수를 거쳐 해당
     메시지를 처리하게 된다.

     실행 과정 중 유의해서 봐야 할 부분이 있다. AfxWinMain( ) 함수 내부에서 호출되는 애
     플리케이션 클래스의 InitInstance( ) 멤버함수가 실행되는 과정을 다시 한 번 생각해 보
     자. 클래스의 멤버함수가 실행되기 위해서는 해당 클래스의 객체가 생성되어야 한다. 클래
     스를 정의했다는 것은 새로운 자료형(멤버함수와 멤버변수로 구성되는)을 정의했다는 뜻으
     로, 이것이 곧 클래스의 멤버를 사용할 수 있다는 것을 의미하지는 않는다. 객체가 생성되
     어야 비로소 메모리가 할당되면서 클래스에 정의된 멤버를 사용할 수 있는 것이다.

     그래도 이해가 되지 않는다면 int 자료형을 예로 들어 보자. int는 정수를 저장할 수 있는
     4byte의 자료형이다. 하지만 int라는 자료형에 값을 저장할 수는 없으며, 값을 저장하려면
     int i; 와 같이 변수를 선언해야 한다. 그러면 실제로 4byte의 메모리가 할당되면서 이 메
     모리 영역에 값을 저장할 수 있게 된다. 여기서 int는 클래스를 정의한 것으로, 변수 i는
     클래스로부터 생성된 객체로 비유할 수 있다.

     그렇다면 AfxWinMain( ) 함수 내부에서 호출된 InitInstance( ) 멤버함수의 객체는 어디
     에 있는 것일까? 이 답을 얻으려면 Class View를 보면 된다. Class View의 Global
     Functions and Variables 목록을 보면 [그림 7-4]처럼 CHelloMFCApp 클래스의 전역 객
     체인 theApp가 선언되어 있는 것을 볼 수 있다.
                                                                                         99



    그림 7-4
theApp 전역 객체




               theApp 객체를 더블클릭하면 theApp 객체를 선언하고 있는 소스 코드가 다음과 같이 나
               타날 것이다.


                CHelloMFCApp theApp;



               이 객체가 바로 애플리케이션을 통틀어 하나밖에 없으며, 또한 반드시 하나만 존재해야 하
               는 애플리케이션 클래스(CHelloMFCApp 클래스)의 전역 객체이고, AfxWinMain( ) 함수에
               서 호출되는 InitInstance( ) 멤버함수는 바로 이 객체에 정의된 멤버함수이다.

               [그림 7-5]는 지금까지 설명한 내용을 도식화한 것이다.


    그림 7-5      WinMain( )
MFC 애플리케이션
     실행 흐름도

                             AfxWinMain( )

                                                               애플리케이션 클래스

                                             InitInstance( )



                                             Run( )




                                                               OnIdle() & Message Loop




                                                                   ExitInstance( )
100

                   [그림 7-5] 하단의 ExitInstance( ) 함수는 CWinApp 클래스의 멤버함수로, 애플리케이션
                   이 종료될 때 Run( ) 멤버함수에서 호출되며, 애플리케이션의 종 처리를 담당하게 된다.

                   정리해 보면, MFC 애플리케이션은 WinMain( ) 함수로부터 시작되고, 실제 시작(초기화)
                   과 종료에 대한 처리는 애플리케이션 클래스에서 담당하고 있음을 알 수 있다.

                   지금까지 설명한 내용은 HelloMFC 프로젝트를 단계별로 디버깅해보면 알 수 있다.
                   Visual C++는 MFC 라이브러리에서 제공되는 전역 함수와 클래스의 소스를 함께 제공하
                   므로 단계별로 디버깅해보면 멤버함수가 실행되는 모습을 눈으로 확인할 수 있다. 관심있
                   는 독자는 11장의 디버깅 과정을 학습한 후 실제로 디버깅해보기 바란다. 단, MFC 라이브
                   러리의 주요 소스 파일이 그대로 오픈되므로 파일 내용을 변경하지 않도록 주의해야 한다.




                   이상으로 MFC 애플리케이션의 구조를 실제 소스 분석을 통해 알아보았다. 다소 어렵다고
                   느낄 수도 있고, MFC 애플리케이션을 개발하는 데 이런 것까지 알아야 할 필요가 있을까
                   생각하는 독자도 있겠지만, 내부 구조의 이해가 선행되지 않으면 Visual C++가 제공하는
                   템플릿 수준 이상의 애플리케이션을 개발하기 어렵다. 어렵게 느껴지더라도 반드시 이해
                   하고 넘어가기 바란다.

                   다음은 이번 장에서 생성한 프로젝트의 전체 소스 파일이다. MFC 라이브러리를 이용해
                   개발되는 프로젝트의 소스는 어느 정도 정형화되어 있고, 분량도 적지 않기 때문에 전체
                   소스를 책에 포함시키는 것은 바람직하지 못한 것 같다. 물론 프로젝트의 전체 소스를 본
                   문에 포함하고 있는 책도 있긴 하지만, 실제 소스를 책으로 보는 경우는 드물고, 불필요하
                   게 두껍게 만드는 부작용이 있다. 따라서 이 책에서는 설명에 필요한 내용이 아니면 프로
                   젝트의 전체 소스를 포함시키지 않을 것이다. 다만 이번 장에서 배운 예제는 MFC의 첫 번
                   째 예제이고 실행 구조를 파악한다는 의미가 있으므로 전체 소스를 포함했다. 중요한 부분
                   은 볼드체로 표기해 두었으니 참고하기 바란다.



      소스 7-1
                    // HelloMFC.h : HelloMFC 응용 프로그램에 대한 주 헤더 파일
      HelloMFC.h
                    //
                    #pragma once


                    #ifndef __AFXWIN_H__
                         #error include 'stdafx.h' before including this file for PCH
                    #endif
                                                              101




               #include "resource.h"         // 주 기호



               // CHelloMFCApp:
               // 이 클래스의 구현에 대해서는 HelloMFC.cpp을 참조하십시오.
               //


               class CHelloMFCApp : public CWinApp
               {
               public:
                    CHelloMFCApp();



               // 재정의
               public:
                    virtual BOOL InitInstance();


               // 구현
                    afx_msg void OnAppAbout();
                    DECLARE_MESSAGE_MAP()
               };


               extern CHelloMFCApp theApp;




  소스 7-2
               // HelloMFC.cpp : 응용 프로그램에 대한 클래스 동작을 정의합니다.
HelloMFC.cpp   //


               #include "stdafx.h"
               #include "HelloMFC.h"
               #include "MainFrm.h"


               #include "HelloMFCDoc.h"
               #include "HelloMFCView.h"


               #ifdef _DEBUG
               #define new DEBUG_NEW
               #endif
102


      // CHelloMFCApp


      BEGIN_MESSAGE_MAP(CHelloMFCApp, CWinApp)
          ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
          // 표준 파일을 기초로 하는 문서 명령입니다.
          ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
          ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
          // 표준 인쇄 설정 명령입니다.
          ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
      END_MESSAGE_MAP()



      // CHelloMFCApp 생성


      CHelloMFCApp::CHelloMFCApp()
      {
          // TODO: 여기에 생성 코드를 추가합니다.
          // InitInstance에 모든 중요한 초기화 작업을 배치합니다.
      }



      // 유일한 CHelloMFCApp 개체입니다.


      CHelloMFCApp theApp;


      // CHelloMFCApp 초기화


      BOOL CHelloMFCApp::InitInstance()
      {
          // 응용 프로그램 매니페스트가 ComCtl32.dll 버전 6 이상을 사용하여 비주얼 스타일을
          // 사용하도록 지정하는 경우, Windows XP 상에서 반드시 InitCommonControls()가
          // 필요합니다.
          // InitCommonControls()를 사용하지 않으면 창을 만들 수 없습니다.
          InitCommonControls();


          CWinApp::InitInstance();


          // OLE 라이브러리를 초기화합니다.
          if (!AfxOleInit())
          {
              AfxMessageBox(IDP_OLE_INIT_FAILED);
                                                                   103



        return FALSE;
    }
    AfxEnableControlContainer();
    // 표준 초기화
    // 이들 기능을 사용하지 않고 최종 실행 파일의 크기를 줄이려면
    // 아래에서 필요 없는 특정 초기화 루틴을 제거해야 합니다.
    // 해당 설정이 저장된 레지스트리 키를 변경하십시오.
    // TODO: 이 문자열을 회사 또는 조직의 이름과 같은
    // 적절한 내용으로 수정해야 합니다.
    SetRegistryKey(_T("로컬 응용 프로그램 마법사에서 생성된 응용 프로그램"));
    LoadStdProfileSettings(4); // MRU를 포함하여 표준 INI 파일 옵션을 로드합니다.
    // 응용 프로그램의 문서 템플릿을 등록합니다. 문서 템플릿은
    // 문서, 프레임 창, 뷰 사이의 연결 역할을 합니다.
    CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(
        IDR_MAINFRAME,
        RUNTIME_CLASS(CHelloMFCDoc),
        RUNTIME_CLASS(CMainFrame),       // 주 SDI 프레임 창입니다.
        RUNTIME_CLASS(CHelloMFCView));
    AddDocTemplate(pDocTemplate);
    // 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);
    // 명령줄에 지정된 명령을 디스패치합니다. 응용 프로그램이 /RegServer, /Register,
    // /Unregserver 또는 /Unregister로 시작된 경우 FALSE를 반환합니다.
    if (!ProcessShellCommand(cmdInfo))
        return FALSE;
    // 창 하나만 초기화되었으므로 이를 표시하고 업데이트합니다.
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();
    // 접미사가 있을 경우에만 DragAcceptFiles를 호출합니다.
    // SDI 응용 프로그램에서는 ProcessShellCommand 후에 이런 호출이 발생해야 합니다.


    AfxMessageBox("Hello MFC!");


    return TRUE;
}




// 응용 프로그램 정보에 사용되는 CAboutDlg 대화상자입니다.
104



      class CAboutDlg : public CDialog
      {
      public:
           CAboutDlg();


      // 대화상자 데이터
           enum { IDD = IDD_ABOUTBOX };


      protected:
           virtual void DoDataExchange(CDataExchange* pDX);   // DDX/DDV 지원


      // 구현
      protected:
           DECLARE_MESSAGE_MAP()
      };


      CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
      {
      }


      void CAboutDlg::DoDataExchange(CDataExchange* pDX)
      {
           CDialog::DoDataExchange(pDX);
      }


      BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
      END_MESSAGE_MAP()


      // 대화상자를 실행하기 위한 응용 프로그램 명령입니다.
      void CHelloMFCApp::OnAppAbout()
      {
           CAboutDlg aboutDlg;
           aboutDlg.DoModal();
      }



      // CHelloMFCApp 메시지 처리기
                                                                        105


소스 7-3
            // MainFrm.h : CMainFrame 클래스의 인터페이스
MainFrm.h
            //



            #pragma once
            class CMainFrame : public CFrameWnd
            {


            protected: // serialization에서만 만들어집니다.
                 CMainFrame();
                 DECLARE_DYNCREATE(CMainFrame)


            // 특성
            public:


            // 작업
            public:


            // 재정의
            public:
                 virtual BOOL PreCreateWindow(CREATESTRUCT& cs);


            // 구현
            public:
                 virtual ~CMainFrame();
            #ifdef _DEBUG
                 virtual void AssertValid() const
                 virtual void Dump(CDumpContext& dc) const
            #endif


            protected: // 컨트롤 모음이 포함된 멤버입니다.
                 CStatusBar m_wndStatusBar;
                 CToolBar   m_wndToolBar;


            // 메시지 맵 함수를 생성했습니다.
            protected:
                 afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
                 DECLARE_MESSAGE_MAP()
            };
106


       소스 7-4       // MainFrm.cpp : CMainFrame 클래스의 구현
      MainFrm.cpp   //


                    #include "stdafx.h"
                    #include "HelloMFC.h"


                    #include "MainFrm.h"


                    #ifdef _DEBUG
                    #define new DEBUG_NEW
                    #endif



                    // CMainFrame


                    IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)


                    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
                         ON_WM_CREATE()
                    END_MESSAGE_MAP()


                    static UINT indicators[] =
                    {
                         ID_SEPARATOR,           // 상태 줄 표시기
                         ID_INDICATOR_CAPS,
                         ID_INDICATOR_NUM,
                         ID_INDICATOR_SCRL,
                    };



                    // CMainFrame 생성/소멸


                    CMainFrame::CMainFrame()
                    {
                         // TODO: 여기에 멤버 초기화 코드를 추가합니다.
                    }


                    CMainFrame::~CMainFrame()
                    {
                    }
                                                                              107




int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;


    if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |
CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
    {
        TRACE0("도구 모음을 만들지 못했습니다.\n");
        return -1;      // 만들지 못했습니다.
    }


    if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
           sizeof(indicators)/sizeof(UINT)))
    {
        TRACE0("상태 표시줄을 만들지 못했습니다.\n");
        return -1;      // 만들지 못했습니다.
    }
    // TODO: 도구 모음을 도킹할 수 없게 하려면 이 세 줄을 삭제하십시오.
    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    EnableDocking(CBRS_ALIGN_ANY);
    DockControlBar(&m_wndToolBar);


    return 0;
}


BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;
    // TODO: CREATESTRUCT cs를 수정하여 여기에서
    // Window 클래스 또는 스타일을 수정합니다.


    return TRUE;
}
108


                      // CMainFrame 진단


                      #ifdef _DEBUG
                      void CMainFrame::AssertValid() const
                      {
                           CFrameWnd::AssertValid();
                      }


                      void CMainFrame::Dump(CDumpContext& dc) const
                      {
                           CFrameWnd::Dump(dc);
                      }


                      #endif //_DEBUG



                      // CMainFrame 메시지 처리기




         소스 7-5
                      // HelloMFCDoc.h : CHelloMFCDoc 클래스의 인터페이스
      HelloMFCDoc.h
                      //



                      #pragma once


                      class CHelloMFCDoc : public CDocument
                      {
                      protected: // serialization에서만 만들어집니다.
                           CHelloMFCDoc();
                           DECLARE_DYNCREATE(CHelloMFCDoc)


                      // 특성
                      public:


                      // 작업
                      public:


                      // 재정의
                      public:
                                                                   109



                       virtual BOOL OnNewDocument();
                       virtual void Serialize(CArchive& ar);


                  // 구현
                  public:
                       virtual ~CHelloMFCDoc();
                  #ifdef _DEBUG
                       virtual void AssertValid() const
                       virtual void Dump(CDumpContext& dc) const
                  #endif


                  protected:


                  // 메시지 맵 함수를 생성했습니다.
                  protected:
                       DECLARE_MESSAGE_MAP()
                  };




     소스 7-6
                  // HelloMFCDoc.cpp : CHelloMFCDoc 클래스의 구현
HelloMFCDoc.cpp
                  //


                  #include "stdafx.h"
                  #include "HelloMFC.h"


                  #include "HelloMFCDoc.h"


                  #ifdef _DEBUG
                  #define new DEBUG_NEW
                  #endif



                  // CHelloMFCDoc


                  IMPLEMENT_DYNCREATE(CHelloMFCDoc, CDocument)


                  BEGIN_MESSAGE_MAP(CHelloMFCDoc, CDocument)
                  END_MESSAGE_MAP()
110


      // CHelloMFCDoc 생성/소멸


      CHelloMFCDoc::CHelloMFCDoc()
      {
          // TODO: 여기에 일회성 생성 코드를 추가합니다.


      }


      CHelloMFCDoc::~CHelloMFCDoc()
      {
      }


      BOOL CHelloMFCDoc::OnNewDocument()
      {
          if (!CDocument::OnNewDocument())
              return FALSE;


          // TODO: 여기에 다시 초기화 코드를 추가합니다.
          // SDI 문서는 이 문서를 다시 사용합니다.


          return TRUE;
      }



      // CHelloMFCDoc serialization


      void CHelloMFCDoc::Serialize(CArchive& ar)
      {
          if (ar.IsStoring())
          {
              // TODO: 여기에 저장 코드를 추가합니다.
          }
          else
          {
              // TODO: 여기에 로딩 코드를 추가합니다.
          }
      }



      // CHelloMFCDoc 진단
                                                                                111



                 #ifdef _DEBUG
                 void CHelloMFCDoc::AssertValid() const
                 {
                      CDocument::AssertValid();
                 }


                 void CHelloMFCDoc::Dump(CDumpContext& dc) const
                 {
                      CDocument::Dump(dc);
                 }
                 #endif //_DEBUG



                 // CHelloMFCDoc 명령




    소스 7-7
                 // HelloMFCView.h : iCHelloMFCView 클래스의 인터페이스
HelloMFCView.h
                 //



                 #pragma once



                 class CHelloMFCView : public CView
                 {
                 protected: // serialization에서만 만들어집니다.
                      CHelloMFCView();
                      DECLARE_DYNCREATE(CHelloMFCView)


                 // 특성
                 public:
                      CHelloMFCDoc* GetDocument() const


                 // 작업
                 public:


                 // 재정의
                 public:
                      virtual void OnDraw(CDC* pDC); // 이 뷰를 그리기 위해 재정의되었습니다.
112


                              virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
                         protected:
                              virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
                              virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
                              virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);


                         // 구현
                         public:
                              virtual ~CHelloMFCView();
                         #ifdef _DEBUG
                              virtual void AssertValid() const
                              virtual void Dump(CDumpContext& dc) const
                         #endif


                         protected:


                         // 메시지 맵 함수를 생성했습니다.
                         protected:
                              DECLARE_MESSAGE_MAP()
                         };


                         #ifndef _DEBUG // HelloMFCView.cpp의 디버그 버전
                         inline CHelloMFCDoc* CHelloMFCView::GetDocument() const
                              { return reinterpret_cast<CHelloMFCDoc*>(m_pDocument); }
                         #endif




            소스 7-8
                         // HelloMFCView.cpp : CHelloMFCView 클래스의 구현
      HelloMFCView.cpp
                         //


                         #include "stdafx.h"
                         #include "HelloMFC.h"


                         #include "HelloMFCDoc.h"
                         #include "HelloMFCView.h"


                         #ifdef _DEBUG
                         #define new DEBUG_NEW
                         #endif
                                                                   113




// CHelloMFCView


IMPLEMENT_DYNCREATE(CHelloMFCView, CView)


BEGIN_MESSAGE_MAP(CHelloMFCView, CView)
    // 표준 인쇄 명령입니다.
    ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()


// CHelloMFCView 생성/소멸


CHelloMFCView::CHelloMFCView()
{
    // TODO: 여기에 생성 코드를 추가합니다.


}


CHelloMFCView::~CHelloMFCView()
{
}


BOOL CHelloMFCView::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: CREATESTRUCT cs를 수정하여 여기서
    // Window 클래스 또는 스타일을 수정합니다.


    return CView::PreCreateWindow(cs);
}


// CHelloMFCView 그리기


void CHelloMFCView::OnDraw(CDC* /*pDC*/)
{
    CHelloMFCDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);


    // TODO: 여기에 원시 데이터에 대한 그리기 코드를 추가합니다.
}
114


      // CHelloMFCView 인쇄


      BOOL CHelloMFCView::OnPreparePrinting(CPrintInfo* pInfo)
      {
          // 기본적인 준비
          return DoPreparePrinting(pInfo);
      }


      void CHelloMFCView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
      {
          // TODO: 인쇄하기 전에 추가 초기화 작업을 추가합니다.
      }


      void CHelloMFCView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
      {
          // TODO: 인쇄 후 정리 작업을 추가합니다.
      }



      // CHelloMFCView 진단


      #ifdef _DEBUG
      void CHelloMFCView::AssertValid() const
      {
          CView::AssertValid();
      }


      void CHelloMFCView::Dump(CDumpContext& dc) const
      {
          CView::Dump(dc);
      }


      CHelloMFCDoc* CHelloMFCView::GetDocument() const // 디버그되지 않은 버전은 인
      라인으로 지정됩니다.
      {
          ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CHelloMFCDoc)));
          return (CHelloMFCDoc*)m_pDocument;
      }
      #endif //_DEBUG


      // CHelloMFCView 메시지 처리기
                                                                                 115
                    Document/View 아키텍처
                    7장에서 학습한 MFC 애플리케이션에서 사용된 클래스 중 CDocument 클래스와 CView 클래스
                    는 MFC 애플리케이션의 로직 구현에서 가장 중요한 클래스라고 할 수 있다. 또한 이 클래스들은
                    각각 독립적으로 구현되는 것이 아니라 서로 유기적인 관계를 맺으며 구현되는 특징이 있다. 이
Chapter 08          번 장에서 Document 클래스와 View 클래스 간의 관계를 알아보도록 하자.




                     Document/View 아키텍처의 구조
                     7장에서 MFC 애플리케이션을 구성하는 클래스 중 데이터를 관리하기 위해 CDocument
                     클래스(정확하게는 CDocument 클래스로부터 파생된 클래스)가 사용되고, 데이터를 클라이언
                     트 영역에 보여주기 위해서는 CView 클래스(CView 클래스로부터 파생된 클래스)가 사용된다
                     고 했다.

                     따라서 CView 클래스로부터 생성된 객체에서는 애플리케이션에서 사용되는 데이터, 즉
                     CDocument 클래스에 정의되어 있는 멤버에 접근할 수 있어야 하고, 반대로 Document
                     클래스로부터 생성된 객체에서는 애플리케이션의 데이터를 화면에 표시할 시점을 알려줄
                     수 있어야 한다. [그림 8-1]은 이런 구조를 도식화한 것이다.


        그림 8-1            View 객체                          Document 객체
    Document/View
아키텍처의 데이터 흐름                 A 값 표시 : 10 ①                    A = 10
                                                  ③                 ②

                             A 값 표시 : 20                      A = 20
                                             ④




                     [그림 8-1]의 흐름을 설명해 보면 다음과 같다.


                        ① - 현재 애플리케이션의 View 영역에 Document 객체의 A 변수 값이 10으로 표시되어
                             있음.

                        ② - A의 값이 20으로 변경됨.

                        ③ - 변경된 값의 갱신을 위해 데이터의 변경이 있다는 것을 View 객체에 통보함(화면의
                             갱신을 요청).

                        ④ - A 값을 갱신하기 위해 View 객체에서 Document 객체의 A 변수에 접근함.
116

      결국 View는 Document 클래스에 정의되어 있는 데이터가 사용자에게 투영되는 장소 역
      할을 하게 되는데, 이러한 기능을 지원하기 위한 MFC 구조를 Document/View 아키텍처
      (Architecture)라고 한다. CDocument 클래스와 CView 클래스에는 이와 같은 Document/
      View 아키텍처의 기능([그림 8-1]에서 ③, ④를 의미함)을 지원하기 위한 멤버함수가 정의되
      어 있다.

      지금까지 설명하던 중에 클래스라는 용어를 사용하기도 하고 객체라는 용어를 사용하기도
      했는데, 두 가지 용어를 혼동하면 안 된다. 클래스는 멤버함수와 멤버변수로 정의되는 자
      료형을 의미하고, 객체는 이 자료형으로부터 생성된(메모리가 할당된) 물리적 자료이다. 이
      것을 일반적인 자료형과 비교해보면 클래스는 int라는 자료형이고, int a; 같은 문장으로
      선언된 변수 a는 객체라고 생각할 수 있다. 따라서 이후의 설명에서 클래스로 표현하면 자
      료형의 정의(변수의 선언) 측면에서 설명하는 것이고, 객체라고 표현하면 실제 프로그램 코
      드에서 사용되는 변수로 이해해야 한다.




      CDocument::UpdateAllViews() 멤버함수
      UpdateAllViews( ) 함수는 CDocument 클래스의 멤버함수로, Document 객체에서 View
      객체로 데이터의 갱신을 요청하기 위한 함수이다. 원형은 다음과 같다.


        void UpdateAllViews(
             CView* pSender,         // 갱신 대상 View. NULL이면 모든 View
             LPARAM lHint = 0L,      // 부가 정보
             CObject* pHint = NULL   // 부가 정보
        );



      UpdateAllViews( ) 함수가 실행되면 View 클래스가 처리를 담당하는 클라이언트 영역이
      무효화(Invalidate)되고, 최종적으로 View 클래스에 정의되어 있는 OnDraw( ) 함수를 실
      행하게 된다. OnDraw( ) 함수에는 일반적으로 애플리케이션의 화면을 구성하기 위한 코
      드가 기술되며, 이 때 Document 클래스의 데이터가 필요하다면 Document 객체의 멤버
      에 접근해서 값을 읽어온 후 화면에 표시하게 된다.

      다음 코드는 Document 클래스의 멤버함수 내에서 UpdateAllViews( ) 함수를 사용하고
      있는 예이다.
                                                                     117



 BOOL CDVExampleDoc::OnNewDocument()
 {
      if (!CDocument::OnNewDocument())
         return FALSE;


      UpdateAllViews(NULL);


      return TRUE;
 }




CView::GetDocument() 멤버함수
앞에서 View 클래스에 정의된 OnDraw( ) 함수는 필요할 때 Document 클래스에 정의되어
있는 멤버에 접근해서 그 값을 읽어야 한다고 했다. 이 때 View 클래스에서 Document 클
래스에 정의되어 있는 멤버에 접근하기 위해 사용되는 함수가 바로 GetDocument( ) 함수이
다. 함수의 원형은 다음과 같다.


     CDocument* GetDocument( ) const;



GetDocument( ) 함수는 View 객체와 연결되어 있는 Document 객체의 포인터를 반환하며,
이 포인터를 통해 Document 객체의 멤버에 접근할 수 있다. 이 때 GetDocument( ) 함수로
접근할 수 있는 대상은 Document 클래스의 멤버변수만이 아니라 Document 클래스의 멤버
함수도 가능하다. 따라서 View 클래스 내에서 Document 클래스에 정의된 멤버함수를 실행
하는 것도 얼마든지 가능하다.

Document 클래스의 UpdateAllViews( ) 멤버함수는 기본 클래스인 CDocument 클래스의
멤버이기 때문에 Application Wizard로 생성한 Document 클래스에 함수의 선언 부분이
나타나지 않는다. 이에 비해 GetDocument( ) 함수는 생성된 View 클래스의 선언부에 함
수의 정의 부분이 존재하는 것을 볼 수 있다. 다음은 Application Wizard로 생성된 View
클래스에 정의되어 있는 GetDocument( ) 함수이다.


 CDVExampleDoc* CDVExampleView::GetDocument() const
 {
      ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDVExampleDoc)));
118


                           return (CDVExampleDoc*)m_pDocument;
                       }



                      이 코드에서 사용된 m_pDocument는 CView 클래스의 멤버로, CDocument 클래스의 포
                      인터로 선언되어 있으며, View 객체와 연결된 Document 객체의 포인터를 의미한다.

                      [그림 8-2]는 지금까지 설명한 Document/View 아키텍처를 그림으로 표현한 것이다.


         그림 8-2
      Document/View
           아키텍처

                                                           UpdateAllViews( )
                                    View                                       Document
                                                            GetDocument( )




                      Document 클래스의 UpdateAllViews( ) 멤버함수와 View 클래스의 GetDocument( ) 멤
                      버함수를 통해 Document/View 아키텍처를 어느 정도 이해할 수 있었을 것이다.

                      여기서 함수들의 이름을 다시 한 번 짚고 넘어가자. UpdateAllViews( ) 함수는 복수형으
                      로 되어 있고, GetDocument( ) 함수는 단수형으로 되어 있다. 이것은 하나의 Document
                      가 여러 개의 View를 가질 수 있다는 것을 의미한다. 7장에서 만들었던 MFC 애플리케이
                      션은 하나의 View 클래스와 하나의 Document 클래스를 가지고 있다. 이런 애플리케이션
                      을 SDI(Single Document Interface)라고 하는데, 노트패드 같은 애플리케이션이 이에 해당
                      한다. 반면에 하나의 Document가 여러 개의 View를 가지거나 여러 개의 Document가 여
                      러 개의 View를 가지는 경우가 있는데, 이런 애플리케이션을 MDI(Multiple Documents
                      Interface) 애플리케이션이라고 하며, 엑셀 등의 애플리케이션이 이에 해당한다. 하지만 어
                      느 경우든 하나의 View는 하나의 Document만 가질 수 있다. 이 때문에 View 클래스에서
                      Document 객체의 포인터를 구하기 위한 함수의 이름은 GetDocument( )이고, Document
                      클래스에서 View 클래스로 데이터의 갱신을 통보하기 위한 함수는 UpdateAllViews( )인
                      것이다.
                                                                       119



Project   Document/View 아키텍처


          지금까지 학습한 Document/View 아키텍처를 확인하기 위한 예제를 만들어 보자. 우리가
          만들 예제는 Document 클래스에서 UpdateAllViews( ) 멤버함수를 이용하여 View 클래
          스에 이벤트를 주고, View 클래스는 이 이벤트를 받아서 다시 Document 클래스의 멤버
          에 접근하게 된다.




          프로젝트 생성
          프로젝트 생성 방법은 앞에서 여러 번 설명되었으므로 그림으로 보여주지는 않고, 과정만
          간략하게 기술하도록 하겠다.


           1. Visual C++를 실행한다

           2. [File] - [New] - [Project] 메뉴를 실행한다.

           3. 프로젝트에서 Visual C++ Projects를 선택한다.

           4. Templates에서 MFC Application을 선택한다.

           5. 프로젝트명에 DVExample을 입력한다.

           6. 프로젝트가 생성될 폴더를 지정한다.

           7. [OK] 버튼을 누른다.

           8. Application Type에서 Single document를 선택한다.

           9. Resource language는 한국어로 선택한다.

           10. [Finish] 버튼을 눌러 프로젝트를 생성한다.




          소스 코드 작성
          Document 클래스의 멤버변수와 멤버함수를 View 클래스에서 접근하는 방법을 구현할 것
          이므로, Document 클래스에 정수형 변수를 선언하고, 이 변수의 값을 변경하는 함수를
          만든 다음, 멤버변수와 멤버함수를 View 클래스에서 GetDocument( ) 멤버함수로 접근하
          는 예제를 만든다. 또 Document/View 아키텍처를 이해하기 위해 Document 클래스에서
          View 클래스로 데이터의 갱신을 위한 신호를 보내는 부분도 함께 구현한다.
120

                        우선 Document 클래스인 CDVExampleDoc 클래스에 정수형으로 m_Data 멤버변수를 추
                        가한다. Class View에서 CDVExampleDoc 클래스를 선택한 다음, 마우스 오른쪽 버튼을
                        누르고 [Add] - [Add Variable] 메뉴를 선택해서 추가하거나, 직접 소스코드에서 추가하
                        면 된다. [그림 8-3]은 마우스 오른쪽 버튼을 눌러 [Add Variable] 메뉴를 선택하는 화면
                        이다.


            그림 8-3
      Class View를 이용한
            멤버변수 추가




                        [그림 8-4]는 m_Data 멤버변수를 추가하는 화면이다.


            그림 8-4
  m_Data 멤버변수 추가




                        멤버변수를 추가하였으면 다음과 같이 생성자 함수에 m_Data 멤버변수를 초기화하기 위
                        한 코드를 작성한다.
                                                                                121



                   CDVExampleDoc::CDVExampleDoc()
                   : m_Data(0)
                   {
                       // TODO: 여기에 일회성 생성 코드를 추가합니다.


                       m_Data = 700;
                   }



                  Visual C++ .NET을 사용하면 생성자 함수에 콜론 초기화를 해주는 코드가 자동으로 추
                  가된다. 하지만 Visual C++ 6.0에서는 이 코드가 자동으로 추가되지 않는다. 따라서 명
                  시적으로 추가한 멤버변수에 대한 초기화 코드를 작성하는 것이 좋다.

                  멤버변수에 대한 코드가 작성되었으면 멤버함수를 추가하도록 한다. 복잡한 로직을 구현할
                  수도 있지만, 예제는 View 클래스에서 Document 클래스의 멤버함수를 실행할 수 있음을
                  확인하는 것이 목적이므로 다음과 같이 간단하게 작성한다. 멤버함수를 추가하는 것은 멤
                  버변수의 추가와 동일한 방법을 사용한다. Class View에서 CDVExample Doc 클래스를 선
                  택하고, 마우스 오른쪽 버튼을 눌러 [Add] - [Add Function] 메뉴를 선택한다. 그러면 멤
                  버함수를 추가하기 위한 대화상자가 나타나는데, Return type에는 void, 함수의 이름
                  (Function name)에는 AddData를 입력한 후, [Finish] 버튼을 누른다.


      그림 8-5
AddData( ) 멤버함수
             추가




                  [Finish] 버튼을 누르면 CDVExampleDoc 클래스에 AddData( ) 멤버함수가 추가될 것이
                  다. 추가된 함수에 다음과 같이 코드를 작성한다.
122


       void CDVExampleDoc::AddData(void)
       {
           m_Data = m_Data + 100;
       }



      다음으로 Document 클래스의 멤버함수인 OnNewDocument( )를 찾아서 다음과 같이 코
      드를 작성한다.


       BOOL CDVExampleDoc::OnNewDocument()
       {
           if (!CDocument::OnNewDocument())
              return FALSE;


           // TODO: 여기에 다시 초기화 코드를 추가합니다.
           // SDI 문서는 이 문서를 다시 사용합니다.


           UpdateAllViews(NULL);


           return TRUE;
       }



      OnNewDocument( ) 멤버함수는 애플리케이션의 Document가 새로 생성될 때 실행되는 멤
      버함수이다. 엑셀이나 아래아한글 같은 프로그램에서 [File] - [New] 메뉴를 실행하면 새로
      운 문서가 생성되는데, 이 시점에 실행되는 멤버함수가 바로 OnNewDocument( ) 함수이
      다. 작성한 UpdateAllViews( ) 멤버함수는 View 영역을 갱신하는 역할을 수행하며, 결과
      적으로 View 클래스의 OnDraw( ) 멤버함수를 실행하게 된다.

      Document 클래스의 멤버가 작성되었다. 이제 View 클래스의 멤버함수를 작성해 보자.
      우선 View 클래스에서 어떤 이벤트가 발생했을 때 Document 클래스의 멤버에 접근하도
      록 할 것인지 결정해야 하는데, 가장 무난한 방법은 마우스 버튼이나 키보드를 눌렀을 때
      접근하게 하는 것이다. 하지만 이렇게 하려면 마우스나 키보드 처리에 대한 것까지 이해해
      야 하므로, 예제에서는 간단히 View 클래스에 구현되어 있는 함수인 OnDraw( ) 함수를
      이용하기로 하자.

      OnDraw( ) 멤버함수는 화면을 갱신해야 할 필요가 있을 때, 즉 화면을 다시 그려야 할 필
      요가 있을 때 실행되는 함수이므로, 애플리케이션을 실행한 후에 윈도우의 크기를 변경하
                                                                   123


거나 윈도우를 최소화했다가 복구할 때 실행된다. 따라서 OnDraw( ) 함수에 Document
클래스에 접근하는 코드를 작성하고, 윈도우의 크기를 변경하는 등의 작업을 했을 때
OnDraw( ) 함수에 작성한 내용의 실행 결과를 확인해보면 될 것이다. CDVExampleView
클래스의 OnDraw( ) 멤버함수에 다음과 같은 코드를 추가하자.


 void CDVExampleView::OnDraw(CDC* /*pDC*/)
 {
     CDVExampleDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);


     // TODO: 여기에 원시 데이터에 대한 그리기 코드를 추가합니다.


     int answer;
     CString msg;
     msg.Format("Add 100 to m_Data?");


     answer = AfxMessageBox(msg, MB_YESNO);


     if(answer == IDYES)
     {
         pDoc->AddData();     // Document 객체의 멤버함수를 호출
     }


     msg.Format("m_Data = %d", pDoc->m_Data);
     AfxMessageBox(msg);
 }



작성한 코드의 내용은 비교적 간단하다. 우선 메시지 박스를 이용해서 Document 클래스
에 정의한 AddData( ) 함수의 실행 여부를 [Yes]나 [No]로 입력받고, [Yes]면 AddData( )
멤버함수를 실행하도록 구현한 다음, Document 클래스에 정의한 m_Data 멤버의 값을 읽
어 메시지 박스로 표시하도록 구현한 것이다. 이 때 Document 클래스의 멤버에 접근하려
면 GetDocument( ) 함수를 사용해야 하지만, 이미 OnDraw( ) 멤버함수의 첫 번째 줄에
GetDocument( ) 함수를 호출하여 Document 객체에 대한 포인터(pDoc)를 구해놓은 상태
이므로, 이 포인터를 그대로 사용해서 구현하였다.

이제 프로젝트를 빌드하여 결과를 확인해 보도록 하자.
124


                     빌드와 실행
                     [Build] - [Build Solution] 메뉴나 [Build DVExample] 메뉴를 실행하여 프로젝트를 빌드
                     한다. 빌드가 완료되면 애플리케이션을 실행해 본다. 그러면 [그림 8-6]처럼 실행과 동시
                     에 메시지 박스가 나타날 것이다.


         그림 8-6
 DVExample 예제 실행 -
      [예] 버튼을 클릭




                     이 메시지 박스는 CDVExampleDoc 클래스의 OnNewDocument( ) 멤버함수에 작성한
                     UpdateAllViews( ) 멤버함수의 실행 결과이다. UpdateAllViews( ) 함수의 실행으로 CDV
                     ExampleView 클래스에 정의되어 있는 OnDraw( ) 멤버함수가 실행되었고, 이 결과에 의
                     해 OnDraw( ) 멤버함수 내부에 작성한 코드에 따라 메시지 박스가 나타난 것이다.

                     여기서 [Yes] 또는 [예] 버튼을 눌러보자. 그러면 AddData( ) 멤버함수가 실행되어
                     Document 클래스에 정의한 m_Data의 값에 100이 더해지고, 이 결과가 [그림 8-7]처럼
                     메시지 박스로 나타날 것이다.


         그림 8-7
 DVExample 예제 실행 -
 [예] 버튼을 눌렀으므로
  기본값에 100이 추가됨
                                                                               125


                    m_Data의 값이 800으로 출력된 것은 AddData( ) 멤버함수의 실행으로 인해 기본값 700
                    에 100이 추가되었기 때문이다.

                    이번에는 윈도우의 크기를 변경해 보자. 윈도우 크기를 변경하면 윈도우 영역을 다시 그려
                    야 하므로 View 클래스의 OnDraw( ) 멤버함수가 실행된다. 따라서 Document 클래스의
                    AddData( ) 멤버함수가 다시 실행된다. 이번에는 [아니오] 버튼을 눌러보자.


        그림 8-8
DVExample 예제 실행 -
  [아니오] 버튼을 클릭




                    [아니오] 버튼을 누르면 AddData( ) 멤버함수가 실행되지 않으므로 m_Data의 값이 변경
                    되지 않는다.


        그림 8-9
DVExample 예제 실행 -
  [아니오] 버튼을 눌렀
   으므로 변화가 없음




                    이처럼 윈도우 영역을 갱신하는 이벤트가 발생할 때마다 OnDraw( ) 함수의 실행결과로
                    메시지 박스가 나타나고, 선택한 값에 따라 Document 클래스의 m_Data 멤버의 값이 메
                    시지 박스로 표시되는 것을 볼 수 있다.
126

      Document/View 아키텍처는 MFC Framework의 골격을 이루는 중요한 내용이다. Win32
      API 애플리케이션이나 기존의 C/C++ 프로그래밍에서는 사용되지 않는 개념이므로 다소
      어렵게 느껴질 수도 있다. 하지만 이 구조를 제대로 알아야 MFC 애플리케이션의 실행 구
      조를 정확히 이해할 수 있고, MDI 애플리케이션 같은 확장된 애플리케이션을 개발할 때도
      쉽게 응용할 수 있으니 확실히 알아두기 바란다.
                                                                          127
             간단한 출력 예제

             이번 장에서는 MFC 애플리케이션에서 화면에 데이터를 출력하는 방법에 대해 알아본다. 출력(문
             자열이나 그래픽)은 분량이 상당히 많고, 이 책에서도 별도의 단원으로 자세히 소개되므로, 여기서

Chapter 09   는 이 책의 예제를 실습하는 데 필요한 기본적인 출력함수에 대해서만 알아보도록 한다.




              Device Context
              DC(Device Context)는 애플리케이션에서 화면에 출력하는 작업을 처리하기 위해 필요한
              출력 속성을 정의하고 있는 자료구조이다. 애플리케이션의 화면에 데이터를 출력하거나
              그래픽 이미지를 출력하려면 여러 가지 요소가 필요하다. 여기에는 출력 대상의 지정, 출
              력 양식에 대한 정보가 포함되는데, 이러한 정보를 DC를 이용해서 관리하게 된다.

              예를 들어, 초등학교 시절 한 번쯤은 그려보았을 풍경화를 그린다고 생각해 보자. 풍경화
              를 그리려면 도화지, 물감, 크레파스 등의 도구가 필요한데 이것을 애플리케이션에 비유한
              다면, 도화지는 애플리케이션 프로그램의 화면(View 영역)으로, 물감과 크레파스 등의 도
              구는 화면에 출력할 데이터의 속성(폰트, 색상, 크기 등)을 지정하기 위한 방법으로 생각할
              수 있다. 이처럼 그림을 그리기 위해 필요한 도화지의 정보, 물감 및 크레파스의 정보를
              관리하기 위한 체제가 바로 DC라고 할 수 있다.

              MFC는 DC를 관리하기 위한 CDC라는 클래스를 제공한다. DC는 CDC 클래스의 객체를 직
              접 이용할 수도 있고, 상황에 따라 CDC 클래스로부터 파생된 서브클래스의 객체를 통해
              사용될 수도 있다. 서브클래스로는 CPaintDC, CClientDC 등이 있다. 다른 DC 관련 클래
              스에 대해서는 그래픽 부분에서 상세히 다루기로 하고, 여기서는 CDC 클래스, 그리고 애
              플리케이션의 View 영역, 즉 클라이언트 영역에서 데이터를 출력하기 위해 주로 사용되는
              CClientDC 클래스에 대해 알아보도록 한다.




              CDC 클래스
              CDC 클래스는 DC를 관리하기 위한 API 함수를 클래스로 구현한 것이다. CDC 클래스는
              화면 출력을 위한 기능이나 프린터 출력에 대한 기능, 그리고 윈도우의 클라이언트 영역에
              대한 출력을 처리하기 위한 기능이 멤버함수 형태로 구현되어 있다.
128

                 DC를 사용하려면 먼저 CDC 클래스의 객체를 생성해야 한다. 그런 다음, DC의 구성 요소
                 를 설정하기 위한 멤버함수를 호출하는 방식으로 DC를 사용할 수 있다. CDC 객체를 통해
                 설정할 수 있는 주요 속성은 다음과 같다.


                  ∙ 그리기 속성의 지정(색상, 선의 굵기, 배경 속성 등)

                  ∙ 화면 좌표계 연산

                  ∙ 영역 처리

                  ∙ 도형 그리기(사각형, 원형, 직선 등)

                  ∙ 문자열 출력


                 CDC 클래스의 파생 클래스로는 [표 9-1]과 같은 것들이 있다.


        표 9-1    클래스              설명
      CDC 클래스의
                 CPaintDC         View 클래스의 OnDraw( ) 함수에서 사용된다.
        파생 클래스
                 CClientDC        윈도우의 클라이언트 영역(View 영역)에서 사용된다.

                 CWindowDC        윈도우 전체 영역에 대한 처리를 할 때 사용된다.

                 CMetaFileDC      메타 파일에 사용된다.



                 이 중에서 윈도우 클라이언트 영역의 출력에 주로 사용되는 클래스는 CClientDC 클래스
                 이다. 앞으로 살펴볼 예제도 CClientDC 클래스를 이용하여 구현된다.

                 CDC 클래스를 이해하려면 DC 관련 API를 어느 정도 알고 있는 것이 좋다. CDC 클래스도
                 결국 DC 관련 API 함수를 클래스로 구현한 것이기 때문이다. 다음 코드는 화면에 사각형
                 을 그리기 위한 API 애플리케이션의 소스이다.


                  HDC hdc      // DC 핸들


                  …


                  hdc = GetDC(hWnd);
                  Rectangle(hdc, 10, 10, 100, 100);
                  ReleaseDC(hWnd, hdc);


                  …
                                                                 129


이 코드는 순수 API 함수로만 구현된 소스이다. 먼저 DC를 구하기 위한 API 함수인
GetDC( ) 함수를 호출하여 화면 영역에 대한 DC를 구한 다음, 이 DC 값을 인자로 하여 사
각형을 그리는 함수인 Rectangle( ) 함수를 호출한다. 그러면 Rectangle( ) 함수는 첫 번
째 인자로 지정된 DC(화면 영역)에 나머지 인자(좌표값)의 값을 사용해서 사각형을 그리게
된다. 그리기가 완료되면 ReleaseDC( ) 함수를 호출해서 사용된 DC를 반환한다.

이 코드를 MFC 애플리케이션으로 바꿔보면 다음과 같을 것이다.


 CClientDC dc(this);
 dc.Rectangle(10, 10, 100, 100);



이 MFC 애플리케이션 소스를 보면 CClientDC 클래스의 객체를 생성한 후, 이 객체의 멤
버함수 형태인 Rectangle( ) 함수를 호출하는 것을 알 수 있다. 그런데 API 애플리케이션
에 있었던 GetDC( ), ReleaseDC( ) 함수의 호출 부분이 보이지 않는다. 이것은 CClientDC
객체가 생성되는 시점과 소멸되는 시점에서 GetDC( ), ReleaseDC( ) 함수가 내부적으로
호출되기 때문이다. 따라서 위의 코드대로라면 dc 객체가 생성되는 순간 GetDC( ) 함수가
호출되고, 위의 코드가 기술되어 있는 모듈을 벗어날 때 ReleaseDC( ) 함수가 호출된다는
것을 짐작할 수 있다.

또한 이 코드에서는 생성된 DC를 저장해 두기 위한 DC 핸들(API 예제의 hdc)도 선언되지
않고 있다는 것을 알 수 있다. 이것은 CClientDC 클래스의 기본 클래스인 CDC 클래스의
데이터 멤버로, 생성된 DC를 저장하기 위한 HDC(DC 핸들) 데이터형의 m_hDC 멤버변수
가 선언되어 있기 때문에 별도의 DC 핸들이 불필요하기 때문이다. 따라서 dc 객체에서
Rectangle( ) 멤버함수를 호출할 때에도 DC가 지정되지 않는다. 내부 멤버로 갖고 있는
값을 사용하기 때문이다.

결국 CClientDC 클래스의 객체를 생성하면 DC 객체가 생성되는데, 이 때 내부적으로
GetDC( ) 함수를 통해 DC가 생성되고, 이 DC는 m_hDC 멤버변수에 저장되며, 이후 이
DC 객체를 통한 멤버함수의 호출은 m_hDC를 사용하게 된다는 것을 알 수 있다. 또 객체
가 파괴되는 시점에서 ReleaseDC( ) 함수가 호출되어 사용된 DC를 반환한다는 것으로 이
해할 수 있다.
130


                TextOut( ) 함수
                TextOut( ) 멤버함수는 CDC 클래스의 멤버함수로 지정된 영역에 문자열을 출력하기 위한
                함수로, 원형은 다음과 같다.


                  virtual BOOL TextOut(
                       int x,
                       int y,
                       LPCTSTR lpszString,
                       int nCount
                  );


                  BOOL TextOut(
                       int x,
                       int y,
                       const CString& str
                  );



                원형을 보면 알 수 있는 것처럼, TextOut( ) 함수는 두 가지 형식으로 사용할 수 있도록
                중복 정의(Overloading)되어 있다. TextOut( ) 함수의 처음 두 가지 인자는 문자열이 출력
                될 좌표값을 의미한다. 이 때 원도우 영역 좌표계의 원점은 화면 좌측 상단이며, x 값은
                왼쪽에서 오른쪽으로, y 값은 위에서 아래로 증가하게 된다. 따라서 TextOut( ) 함수의 좌
                표로 (0, 0)을 지정하면 화면의 좌측 상단 코너에 문자열이 출력된다.


       그림 9-1
 클라이언트 영역 좌표계

                                            x 좌표
                       좌표 (0,0)



                  y 좌표




                다음은 TextOut( ) 함수의 사용 예제이다.
                                                                        131



           dc.TextOut(10, 10, "TextOut() Example1", 18);


           CString msg = "TextOut() Example2";
           dc.TextOut(10, 30, msg);




Project   클라이언트 영역에 문자열 출력


          윈도우 클라이언트 영역에 문자열을 출력하는 함수는 CDC 클래스의 TextOut( ) 함수이
          다. 이번 예제에서는 8장에서 작성한 DVExample 프로젝트를 이용하여 클라이언트 영역
          에 문자열을 출력하는 코드를 작성해 보겠다.




          소스코드 수정
          이번 예제는 프로젝트를 새로 생성하는 것이 아니라 8장에서 작성한 DVExample 프로젝
          트 예제를 이용한다. Visual C++를 실행하고, DVExample 프로젝트를 오픈한 다음,
          Class View에서 CDVExampleView 클래스의 OnDraw( ) 멤버함수를 찾아 코드를 다음과
          같이 수정한다.


           void CDVExampleView::OnDraw(CDC* pDC)
           {
               CDVExampleDoc* pDoc = GetDocument();
               ASSERT_VALID(pDoc);


               // TODO: 여기에 원시 데이터에 대한 그리기 코드를 추가합니다.


               int answer;
               CString msg;
               msg.Format("Add 100 to m_Data?");


               answer = AfxMessageBox(msg, MB_YESNO);
132


           if(answer == IDYES)
           {
               pDoc->AddData();
           }


           msg.Format("m_Data = %d", pDoc->m_Data);
           //AfxMessageBox(msg);
           pDC->TextOut(10, 10, msg);
       }



      이 소스는 CDVExampleDoc 클래스에 정의되어 있는 m_Data 멤버변수의 값을 메시지 박
      스로 표시했던 것을 화면에 출력하는 방식으로 변경한 것이다. 이 때 Visual C++ .NET을
      사용하는 경우에는 OnDraw( ) 함수의 인자에 주석처리가 되어 있으므로 주석을 제거한다.

      소스에서 주의깊게 살펴봐야 할 부분이 있다. 앞에서 설명할 때 화면에 문자열을 출력하기
      위해서 DC 객체를 생성해야 하고, 클라이언트 영역의 출력을 위해서 CClientDC 클래스의
      객체를 사용한다고 했다. 하지만 OnDraw( ) 멤버함수의 경우는 조금 다르다. OnDraw( )
      멤버함수는 이름에서도 느낄 수 있겠지만, 화면에 무언가를 출력하기 위한 함수이다.
      Visual C++는 OnDraw( ) 멤버함수의 경우 기본값으로 출력작업을 위한 CDC 클래스의
      객체 포인터를 함수의 인자로 전달해 주고 있다. 따라서 별도의 DC를 생성할 필요없이 인
      자로 전달된 DC 객체 포인터를 이용해서 출력작업을 수행할 수 있다.

      소스를 보면 알 수 있겠지만, 여기서도 CClinetDC 클래스의 객체를 생성하지 않고 인자로
      전달된 객체 포인터 pDC를 이용해서 TextOut( ) 함수를 호출하도록 구현하고 있다. 물론
      View 클래스에 새로운 멤버함수를 추가하고 이 함수 내부에서 화면 출력을 위한 코드를
      작성한다면 당연히 CClientDC 클래스의 객체를 생성해야 할 것이다.




      빌드와 실행
      [Build] - [Build Solution] 메뉴나 [Build DVExample] 메뉴를 실행하여 프로젝트를 빌드
      한다. 빌드가 완료되면 애플리케이션을 실행한다. 그러면 [그림 9-2]처럼 실행과 동시에
      메시지 박스가 나타날 것이다.
                                                                                  133



        그림 9-2
수정된 DVExample 예제




                     여기서 [Yes] 또는 [예] 버튼을 눌러보자. 그러면 CDVExampleDoc 클래스의 m_Data 멤
                     버변수의 값이 메시지 박스로 표시되지 않고, [그림 9-3]처럼 화면 영역에 출력되는 것을
                     볼 수 있다.


        그림 9-3
    TextOut( ) 함수의
           실행 결과




                     이제 8장에서 했던 것처럼 여러 가지 방법으로 테스트 해보자. 그러면 메시지 박스 대신에
                     화면으로 m_Data의 값이 출력될 것이다. 다만 m_Data의 값이 출력될 때 처음부터 화면
                     에 나타나는 것이 아니라, 숫자의 증가 여부를 묻는 메시지 박스가 타나날 때는 화면이 지
                     워지고, 메시지 박스의 버튼을 누른 후에 m_Data의 값이 나타나는 것을 볼 수 있다. 이것
                     은 OnDraw( ) 멤버함수가 실행될 때 화면이 갱신되기 때문이다. 화면이 갱신된다는 것은
                     화면을 깨끗이 지운 다음 다시 출력한다는 뜻이므로, OnDraw( ) 멤버함수의 마지막 부분
                     에 기술된 TextOut( ) 함수의 실행 결과는 마지막에 나타나게 된다.


                     이상으로 DC를 이용한 화면 출력에 대해 간단하게 알아보았다. DC를 이용한 그래픽 출력
                     방법은 더 다양하며 이 책에서도 자세히 다루고 있다. 이 장에서는 앞으로 학습하게 될 예
                     제의 작성을 위한 기본 출력방법을 알아본 것으로 만족하기 바란다.
134
                 콘솔 애플리케이션
                 지금까지 API와 MFC를 이용해 윈도우 애플리케이션을 개발하는 방법을 이론과 예제 프로젝트를
                 통해 알아보았다. 그렇다면 Visual C++를 이용해서 DOS 환경(정확히는 명령 프롬프트)에서 실행되
                 는 애플리케이션은 만들 수 없는 것일까? 물론 그렇지 않다. Visual C++로도 명령 프롬프트 환
Chapter 10       경에서 실행되는 텍스트 기반의 애플리케이션을 만들 수 있는데, 이러한 애플리케이션을 콘솔 애
                 플리케이션이라고 한다.




                  콘솔 애플리케이션
                  Visual C++ 관점에서 콘솔(Console) 애플리케이션을 쉽게 설명하면, main( ) 함수를 엔트
                  리 포인트로 갖는 텍스트 기반의 애플리케이션이라고 할 수 있다(윈도우 애플리케이션은
                  WinMain( ) 함수를 엔트리 포인트로 갖는다).

                  하지만 Visual C++에서의 콘솔 애플리케이션은 일반적인 텍스트 기반의 애플리케이션과
                  는 약간의 차이가 있다. 단순한 텍스트 기반 애플리케이션은 DOS 환경이나 윈도우 운영
                  체제의 명령 프롬프트에서 실행되고 윈도우적인 요소, 즉 GUI(Graphical User Interface)
                  요소를 갖지 않지만, 콘솔 애플리케이션은 간단한 형태의 GUI 요소를 가질 수 있다. [그
                  림 10-1]은 윈도우 애플리케이션에서 많이 사용되는 메시지 박스가 콘솔 애플리케이션에
                  서 나타나고 있는 화면이다.


       그림 10-1
 메시지 박스를 표시하고
 있는 콘솔 애플리케이션




                  콘솔 애플리케이션은 단순한 텍스트용 프로그램이나 사용자 인터페이스가 거의 필요없는
                  애플리케이션, 또는 백그라운드로 실행되는 애플리케이션의 개발에 주로 사용된다. 또 C
                  언어나 C++ 언어를 배우기 시작하는 독자들이 처음 만들어보는 프로그램도 대부분 콘솔
                  애플리케이션인 경우가 많다.
                                                                       135



      Project   콘솔 애플리케이션 만들기




                프로젝트 생성
                프로젝트 생성 과정은 다음과 같다.


                 1. Visual C++를 실행한다.

                 2. [File] - [New] - [Project] 메뉴를 실행한다.

                 3. 프로젝트에서 Visual C++ Projects를 선택한다.

                 4. Templates에서 Win32 Project를 선택한다.

                 5. [OK] 버튼을 누른다.

                 6. 프로젝트명으로 ConsolePGM1을 입력한다.

                 7. 프로젝트가 생성될 폴더를 지정한다.

                 8. [OK] 버튼을 누른다.

                 9. Application Wizard에서 Application Settings를 선택한다.

                 10. Application type에 Console application을 지정한다.

                 11. [Finish] 버튼을 눌러 프로젝트를 생성한다.


     그림 10-2
콘솔 애플리케이션 설정
136


      소스코드 작성
      프로젝트를 생성하고 Class View를 보면, Global Functions and Variables 항목에서
      _tmain( ) 함수 또는 main( ) 함수를 찾을 수 있을 것이다. 이 함수를 더블클릭해서 오픈
      하고, 다음과 같이 코드를 작성한다.


       int _tmain(int argc, _TCHAR* argv[])
       {
           int i;
           int j;
           int sum;


           printf("Console Application.\n");


           printf("Enter 1st number : ");
           scanf("%d", &i);


           printf("Enter 2nd number : ");
           scanf("%d", &j);


           sum = i + j;
           printf("Sum = %d\n", sum);


           return 0;
       }



      평범한 소스이므로 따로 설명할 필요는 없을 것이다. 다만 소스 상단에 include되어 있는
      stdafx.h 헤더 파일은 설명할 필요가 있겠다. stdafx.h 헤더 파일은 Precompiled 헤더 파
      일을 만들기 위해 사용되는 것으로, 우리말로 하면 미리 컴파일해놓은 헤더 파일 정도로
      풀이할 수 있다. C/C++ 프로그램을 작성하다 보면 소스마다 include되는 헤더 파일들이
      있다. 이런 헤더 파일은 새로운 소스를 추가할 때마다 계속 include해줘야 하는 경우가 많
      은데, 개수가 많을 때는 번거로운 작업이 될 수도 있다. 따라서 소스마다 빈번하게
      include되는 헤더 파일을 stdafx.h 헤더 파일에 미리 include해두고, 실제 소스에서는
      stdafx.h 헤더 파일만 include하면 보다 간편하게 프로그램을 작성할 수 있을 것이다.

      또한 stdafx.h 파일에 include되는 파일(일반적으로 다른 헤더 파일)은 내용이 잘 변경되지
      않으므로, 이 파일을 미리 컴파일 해두고 빌드할 때마다 사용하면 전체적으로 빌드에 소요
      되는 시간을 줄일 수 있는 장점이 있다. 프로젝트가 생성된 폴더의 Debug 폴더(실행파일이
                                                                        137


생성되는 폴더)를 보면 pch라는 확장자를 가진 파일을 찾을 수 있는데, 이 파일이 바로
Precompiled 헤더 파일이다.

Precompiled 헤더 파일은 종종 빌드와 관련된 문제를 일으키기도 한다. 예를 들어, 인터
넷에서 다운로드한 프로젝트 소스를 빌드하거나 다른 컴퓨터에서 작성하던 프로젝트를 복
사해서 빌드할 때, Precompiled 헤더 파일에 관련된 오류가 발생하면서 빌드되지 않는 경
우가 있다. 이것은 Precompiled 헤더 파일이 생성된 컴퓨터의 환경(운영체제, Visual C++
버전 등)이 다르기 때문에 발생하는 것으로, Precompiled 헤더 파일을 지우고 다시 빌드하
거나 단순히 Rebuild하면 재작성해주므로 문제를 해결할 수 있다.

또한 위의 소스를 보면 다음과 같은 _tmain( ) 함수를 볼 수 있다.


 int _tmain(int argc, _TCHAR* argv[])



콘솔이나 DOS 환경에서 실행되는 C/C++ 프로그램의 진입점(Entry Point)은 main( ) 함수
일텐데, Visual C++에서 생성한 소스를 보면 main( ) 함수 대신 _tmain( ) 함수가 있는
것을 볼 수 있다(Visual C++ 6.0으로 콘솔 프로젝트를 만들면 main( ) 함수가 생성된다. 단 MFC
지원 옵션을 선택하면 _tmain( ) 함수가 생성된다).

이것은 생성되는 애플리케이션의 문자 코드체계와 관계가 있다. 가장 일반적으로 사용되
는 코드 체계는 ASCII 코드이다. 하지만 Visual C++는 ASCII 코드 외에 Unicode와
Multibyte-character를 추가로 사용할 수 있는데, 이 코드 체계들은 궁극적으로 애플리
케이션의 Localization을 구현하기 위해 사용된다.

예를 들어 이 코드 체계들은 A라는 문자를 표현하기 위해 필요한 Byte 수가 서로 같지 않
다. 따라서 하나의 코드 체계를 기준으로 만들어진 애플리케이션을 다른 코드 체계를 사용
하는 컴퓨터에서 실행시키면 정상적으로 실행되지 않는 경우가 발생할 수 있으며, 이런 프
로그램의 소스를 수정하려면 무척 많은 시간과 노력이 필요하다. 따라서 Visual C++에서
제공하는 함수를 코드 체계별로 미리 만들어 두고, 프로그램 소스에는 이 함수 이름을 직
접 기술하는 대신 #define으로 정의한 임시 함수 이름을 사용한 다음, 링크가 이루어지는
시점에서 지정된 코드 체계별로 작성된 함수를 링크해주면 소스의 수정없이 동일한 소스
를 사용해서 여러 코드 체계를 동시에 만족시키는 애플리케이션을 만들 수 있다. 이와 같
은 코드 체계로는 _UNICODE(Unicode), _MBCS(Multibyte Character Set), 그리고 SBCS
(Singlebyte Character Set)이 있다.
138

                         다시 _tmain( ) 함수를 살펴보도록 하자. 마우스를 _tmain( ) 함수 위로 이동하면 [그림
                         10-3]처럼 _tmain( ) 함수가 define으로 정의된 것을 확인할 수 있다.


           그림 10-3
      define으로 정의된
          _tmain( ) 함수




                         정의된 내용을 보면 _tmain( ) 함수는 wmain( ) 함수로 치환되어 실행될 것으로 예상된다.
                         Solution Explorer를 이용하여 stdafx.h 헤더 파일을 오픈하면 다음과 같은 코드를 발견
                         할 수 있을 것이다.


                          …
                          #include <stdio.h>
                          #include <tchar.h>
                          …



                         여기서 <tchar.h> 부분을 마우스 오른쪽 버튼으로 클릭하면 팝업 메뉴가 나타난다. 여기
                         서 [Open Document <tchar.h>] 메뉴를 선택하면 tchar.h 헤더 파일이 나타난다. 이 헤
                         더 파일에서 _UNICODE와 SBCS, _MBCS에 대한 설정 내용을 찾아보면 다음과 같은 코드
                         를 확인할 수 있다.


                          #ifdef _UNICODE


                          …


                          /* ++++++++++++++++++++ UNICODE ++++++++++++++++++++ */


                          #include <wchar.h>


                          …


                          /* Program */
                                                                  139



 #define _tmain        wmain
 #define _tWinMain     wWinMain


 …


 #else   /* ndef _UNICODE */


 /* ++++++++++++++++++++ SBCS and MBCS ++++++++++++++++++++ */


 …


 #include <string.h>



 /* Program */


 #define _tmain        main
 #define _tWinMain     WinMain


 …


 #endif /* _UNICODE */



이 코드를 보면, 코드 체계가 _UNICODE면 _tmain을 wmain으로 치환하고, SBCS나
_MBCS면 main으로 치환하므로, 결국 _tmain( ) 함수는 wmain( ) 함수와 main( ) 함수로
변경된다는 것을 확인할 수 있다. wmain( ) 함수의 w는 wide를 뜻하며, Unicode를 사용
하는 애플리케이션의 진입점으로 사용된다. 이와 같은 코드 체계별 처리 방식을 Generic
Text Mapping이라고 하며, Worldwide하게 사용되는 애플리케이션의 개발은 대부분 이
방식으로 개발된다.

지금까지 설명한 내용을 잘 이해하고 tchar.h 헤더 파일의 내용을 살펴보았다면, Win32
API 애플리케이션의 진입점으로 왜 _tWinMain( ) 함수가 정의되어 있는지 알 수 있을 것
이다. _tWinMain( ) 함수는 코드 체계에 따라 wWinMain( ) 함수 또는 WinMain( ) 함수로
치환되어 빌드된다.
140


                빌드와 실행
                소스코드의 작성이 완료되었으면 프로젝트를 빌드하고 실행해 보자. 간단한 예제이므로
                자세한 설명은 생략하겠다. [그림 10-4]는 실행 화면이다.


      그림 10-4
    메시지 박스 콘솔
 애플리케이션 예제 실행




                실행 화면의 마지막 줄에 나오는 Press any key to continue라는 메시지는 애플리케이션
                에서 출력된 것이 아니라 Visual C++에서 표시한 것이다. 콘솔 애플리케이션을 Visual
                C++ 통합 환경에서 실행하면 애플리케이션 종료시에 콘솔 윈도우도 함께 사라지기 때문
                에 애플리케이션의 실행 결과를 알 수 없는 문제가 발생한다. 이런 문제를 해결하기 위해
                사용자의 입력을 받은 후 콘솔 윈도우가 종료되도록 한 것이다. 명령 프롬프트를 따로 실
                행하고 이 명령 프롬프트에서 애플리케이션을 실행하면 이와 같은 메시지가 나타나지 않
                는다.




                콘솔 애플리케이션에서 메시지 박스 표시하기
                콘솔 애플리케이션은 일반적인 DOS 프로그램과 달리 GUI 요소를 가질 수 있다고 했다.
                이번에는 앞에서 작성한 ConsolePGM1 예제에 GUI 요소 중 하나인 메시지 박스를 표시하
                는 방법을 알아보자. 앞의 예제에 다음과 같은 코드(굵게 표시된 부분)를 추가한다.


                 #include "stdafx.h"
                 #include <windows.h>


                 int _tmain(int argc, _TCHAR* argv[])
                 {
                      int i;
                                                                                141



                  int j;
                  int sum;


                  printf("Console Application.\n");


                  printf("Enter 1st number : ");
                  scanf("%d", &i);


                  printf("Enter 2nd number : ");
                  scanf("%d", &j);


                  sum = i + j;
                  printf("Sum = %d\n", sum);


                  MessageBox(NULL, "Console Application.", "Console", MB_OK);


                  return 0;
              }



             소스 첫 부분에 추가한 include된 windows.h 헤더 파일은 윈도우 애플리케이션을 만들기
             위해 include해야 하는 헤더 파일이고, 마지막에 추가한 MessageBox( ) 함수는 메시지 박
             스를 표시하기 위한 함수이다. 이 소스를 빌드하고 실행하면 [그림 10-5]와 같은 결과를
             확인할 수 있다(책과 함께 제공되는 소스에는 ConsolePGM2 프로젝트로 되어 있다).


   그림 10-5
메시지 박스를 표시
 하고 있는 애플리
 케이션 예제 실행




             이상으로 Visual C++를 이용한 콘솔 애플리케이션의 개발에 대해 알아보았다. DOS 프로
             그램과 비슷한 점도 있지만 메시지 박스 같은 윈도우 요소를 표시할 수도 있다는 점을 기
             억해 두자.
142
                       디버깅

                       오류없이 한 번에 정상적으로 실행되는 프로그램을 개발하는 것은 거의 불가능하다. 물론 단순한
                       문장만 사용하는 간단한 프로그램은 한 번에 작성할 수도 있겠지만, 어느 정도의 입출력 기능과

Chapter 11             처리 로직을 갖고 있는 프로그램을 개발할 때, 오류는 반드시 넘어야 할 장벽이라고 할 수 있다.




                        오류의 종류
                        프로그램에서 발생하는 오류(Error)는 크게 문법적 오류와 논리적 오류로 나눌 수 있다.
                        문법적 오류는 말 그대로 프로그래밍 언어의 문법을 지키지 않아서 발생하는 오류로, 컴파
                        일이나 링크 단계에서 대부분 발견되기 때문에 큰 어려움없이 수정할 수 있다. 하지만 논
                        리적 오류는 문법적으로는 틀린 부분이 없지만 변수를 잘못 사용했다거나 또는 처리해야
                        할 로직을 잘못 구현했기 때문에 발생하는 문제이므로, 어디서 오류를 일으켰는지 찾아내
                        기도 어렵고, 수정 작업도 그리 쉽지는 않다.




                        문법적 오류
                        Visual C++로 컴파일하는 도중 문법적 오류가 발생하면 오류의 내용과 원인, 위치 등이
                        Output 윈도우에 나타난다. 또 나타난 오류를 더블클릭하면 발생한 위치로 이동하기 때문
                        에 별다른 어려움 없이 수정할 수 있다. [그림 11-1]과 [그림 11-2]는 TextOut( ) 함수를
                        TestOut( )으로 잘못 작성했을 때, 빌드 도중 오류 메시지를 보여주는 Output 윈도우와
                        수정해야 할 오류 목록을 보여주는 Task List 윈도우이다.


            그림 11-1
      오류 메시지가 나타난
          Output 윈도우
                                                                          143



      그림 11-2
오류 메시지가 나타난
  Task List 윈도우




                  Task List 윈도우의 내용 중 C2039는 에러 코드를 표시하고, 이어서 나타나는 메시지는
                  에러의 원인을 보여준다. MSDN 같은 도움말 시스템이 설치되어 있다면, 이 리스트를 선
                  택하고 [F1] 키를 눌러 해당 에러 코드에 대한 도움말을 볼 수 있다. 또한 이 목록을 더블
                  클릭하면 오류가 발생한 위치로 이동한다. [그림 11-3]은 오류 목록을 더블클릭했을 때 오
                  류가 발생한 위치로 이동한 화면이다.


      그림 11-3
오류가 발생한 위치로
     이동한 화면




                  여기서 발생한 오류를 수정하고, 다시 컴파일하면 디버깅이 완료된다.




                  논리적 오류
                  논리적 오류는 발생한 부분이 눈에 보이지 않으므로 문법적 오류에 비해 발견하기도 어렵
                  고 수정도 쉽지 않다. Visual C++에서는 이러한 논리적 오류를 수정하기 위한 디버깅 툴
                  을 제공한다. 디버깅 툴을 이용하면 프로그램을 수행하면서 사용된 변수의 값을 모니터링
                  하거나 변경할 수 있고 프로그램의 수행 흐름을 제어할 수 있기 때문에 프로그램에서 문제
                  를 일으키는 부분을 효과적으로 찾아낼 수 있다.
144


                   Visual C++의 디버깅 기능
                   [그림 11-4]는 Visual C++ .NET의 디버깅 메뉴와 툴바이다.


         그림 11-4
      디버깅 메뉴와 툴바




                   디버깅은 기본적으로 작성된 소스 코드를 라인 단위로 실행하면서 프로그램에 사용된 변
                   수의 값을 검사하거나 프로그램의 흐름이 어떻게 흘러가는지 검사하는 방법으로 이루어진
                   다. 하지만 소스 코드를 라인 단위로 따라가는 단순한 방식으로는 디버깅에 한계가 있다.
                   검증이 필요없는 소스도 있을 수 있고, 다른 부분으로 제어를 이동하여 디버깅해야 할 경
                   우도 있을 수 있기 때문이다. Visual C++에서 이러한 기능을 어떻게 제공하고 있는지 확
                   인해 보도록 하자.




                   Step Into
                   [Debug] - [Step Into] 메뉴를 선택하거나 [F11] 키를 눌러 실행하는 Step Into는 소스 코
                   드를 라인 단위로 실행한다. 만약 소스 코드에 함수의 호출이 있다면 해당 함수 내부로 이
                   동하여 디버깅을 계속한다.




                   Step Over
                   Step Over는 Step Into와 동일한 기능을 수행한다. 단, 함수를 만나면 함수의 내부로 진
                   행하는 것이 아니라, 함수를 실행한 후 함수 다음 위치에서 디버깅을 계속하게 된다. 따라
                   서 오류의 검증이 끝난 함수를 건너뛰려고 할 때 유용하게 사용할 수 있다. [Debug] -
                   [Step Over] 메뉴를 선택하거나 [F10] 키를 눌러 실행한다.
                                                                               145



          Step Out
          Step Out은 [Debug] - [Step Out] 메뉴를 선택하거나 [Shift] - [F11] 키를 눌러 실행하
          며, 현재 디버깅중인 함수를 호출한 다음 문장으로 제어가 이동된다. 즉, 함수에서 return
          되는 효과가 있다. 단, Step Out을 실행한 시점까지만 실행하고 return되는 것이 아니라,
          Step Out을 실행한 시점부터 return 문장을 만날 때까지 코드를 실행한 후에 return 문
          장을 만나면 return하게 된다.



          Breakpoint
          Breakpoint는 중단점을 뜻하는 것으로, 애플리케이션이 실행되다가 Breakpoint가 설정
          된 위치에서 멈추게 된다. 디버깅해야 하는 위치가 애플리케이션의 끝 부분일 경우에는 디
          버깅 작업을 할 때마다 해당 위치까지 가기 위해 [F10] 키를 계속 누르는 수고를 해야 하
          는데, 이 때 디버깅할 위치에 중단점을 설정해 두고 디버깅하면, 설정된 위치에서 자동으
          로 멈추므로 효과적인 디버깅을 할 수 있다. [Debug] - [New Breakpoint] 메뉴를 실행하
          거나 에디터의 왼쪽 경계선 부분을 마우스로 클릭하면 중단점이 지정된다. 그리고 설정된
          중단점을 클릭하면 중단점이 해제된다. [그림 11-5]는 중단점을 설정하는 화면이다.


그림 11-5
중단점 설정




          Visual C++ 6.0의 경우에는 왼쪽 경계선 부분에서 마우스 오른쪽 버튼을 클릭해 나타나
          는 메뉴에서 [Insert/Remove Breakpoint] 메뉴를 선택하면 된다.



          Watch 윈도우
          Watch 윈도우는 디버깅 작업 도중 검사를 원하는 변수나 수식의 값을 보기 위해 사용한
          다. 모두 4개의 Watch 윈도우를 사용할 수 있는데, [Debug] - [Windows] - [Watch] 메
          뉴를 선택하여 실행할 수 있다. [그림 11-6]은 Watch Window를 통해 WinMain( ) 함수의
          nCmdShow 인자 값을 검사하고 있는 화면이다.
146


       그림 11-6
      Watch 윈도우




                  화면의 두 번째 줄에 있는 수식 nCmdShow+1은 아무런 의미도 없지만, Watch Window에
                  서 변수만이 아니라 수식의 연산 결과도 볼 수 있다는 것을 보여주기 위해 제시한 것이다.
                  그리고 세 번째와 네 번째 수식은 단순한 수식만이 아닌 조건문도 검사할 수 있다는 것을
                  보여주기 위한 예제이다. Visual C++ 6.0에서는 [View] - [Debug Windows] - [Watch]
                  메뉴를 실행하면 된다.




                  Autos, Locals, Memory, Registers 윈도우
                  Autos 윈도우는 현재 디버깅중인 문장과 이전 문장에서 사용되고 있는 모든 변수를 보여
                  준다. 따라서 2개의 문장(또는 모듈)에서 사용하고 있는 변수를 검사할 수 있으며, 연속된
                  문장에서 사용되고 있는 변수를 검사할 때 유용하다. [F10]이나 [F11] 키를 눌러 디버깅을
                  계속하면 해당 위치와 이전 위치에서 사용되는 변수를 실시간으로 반영해서 보여준다. [그
                  림 11-7]은 Autos Window 화면을 보여주고 있다.


       그림 11-7
      Autos 윈도우




                  이 그림을 보면 현재 디버깅중인 문장(왼쪽에 화살표(⇨)가 있는 문장)과 이전 문장에서 사용
                  된 변수인 i, j, k, s1, s2 변수의 목록이 나타나는 것을 볼 수 있다. s2는 아직 실행 전이
                  기 때문에 Garbage 값이 저장되어 있다. 여기서 디버깅을 계속하면 k, s1, s2가 목록에
                  나타날 것이다.
                                                                          147


             Locals Window는 Autos Window와 비슷한 내용을 보여주지만, 현재 디버깅중인 위치의
             범위(Scope)에서 사용 가능한 지역 변수를 보여준다는 점이 다르다. [그림 11-8]은 Locals
             Window 화면을 보여주고 있다.


 그림 11-8
Locals 윈도우




             Memory Window와 Registers Window는 디버깅중인 애플리케이션의 메모리와 CPU의 레
             지스터 변수를 검사하기 위해 사용되는데, 특별한 경우가 아니면 잘 사용되지 않는다.




             Edit and Continue
             Edit and Continue 기능은 디버깅 도중에 소스를 변경해야 할 때, 디버깅을 중단하지 않
             고 변경된 내용을 적용하여 계속 디버깅을 수행하는 기능으로, Visual C++ 6.0부터 적용
             된 기능이다. 6.0 이전에는 디버깅 도중에 소스를 수정해야 할 필요가 있으면, 디버깅을
             중단하고 소스를 수정한 후에 다시 디버깅해야 하는 불편함이 있었다. Edit and Continue
             기능을 이용하면 디버깅을 중단하지 않고 계속 실행할 수 있으므로 효율적으로 디버깅을
             할 수 있다.




             이상으로 Visual C++에서 제공하는 디버깅 기능에 대해 간단히 설명하였다. 하지만 100
             번 읽는 것보다는 실제로 한 번 해보는 것이 이해하는 데 훨씬 도움이 될 것이다. 지금부
             터 작성할 예제를 통해 디버깅 방법을 완전히 숙지하기 바란다.
148


      Project   디버깅




                프로젝트 생성
                프로젝트 생성 과정은 다음과 같다. Visual C++ 6.0에서는 Win32 Console Application
                프로젝트를 생성한다.


                 1. Visual C++를 실행한다.

                 2. [File] - [New] - [Project] 메뉴를 실행한다.

                 3. 프로젝트에서 Visual C++ Projects를 선택한다.

                 4. Templates는 Win32 Application을 선택한다.

                 5. 프로젝트명으로 DebugExample을 입력한다.

                 6. 프로젝트가 생성될 폴더를 지정한다.

                 7. [OK] 버튼을 눌러 다음 단계로 진행한다.

                 8. Application type에서 Console application을 선택한다.

                 9. [Finish] 버튼을 눌러 프로젝트를 생성한다.




                소스코드 작성
                효과적인 디버깅 작업을 위해 main( ) 함수 외에 별도의 함수를 하나 작성하고, 전역변수
                도 선언해서 지역변수와 전역변수가 디버깅 윈도우에 어떠한 형태로 나타나는지 확인해
                보도록 하자.

                _tmain( ) 함수의 내용과 전역변수, 함수를 다음과 같이 작성한다.


                 #include "stdafx.h"


                 int i = 70;
                 int GetSumAlpha(int p, int q);


                 int _tmain(int argc, _TCHAR* argv[])
                 {
                    int i, j, k;
                                149



    int x, y, z;
    int s1, s2, s3, s4;


    i = 10;
    j = 20;
    k = 30;


    x = 100;
    y = 200;
    z = 300;


    s1 = i + j;
    s2 = s1 + k;


    printf("s1 = %d\n", s1);
    printf("s2 = %d\n", s2);


    {
        int i;


        i = 50;
        s3 = i + ::i;
    }


    printf("s3 = %d\n", s3);


    s4 = GetSumAlpha(s1, s2);


    printf("s4 = %d\n", s4);


    return 0;
}


int GetSumAlpha(int p, int q)
{
    int alpha;
    int sum;


    alpha = 80;
    sum = p + q + alpha;


    return sum;
}
150

                     사실 이런 코드는 내용면에서 그다지 적절한 코드는 아니다. 이 소스는 디버깅 작업을 수
                     행할 때 Visual C++에서 제공하는 디버깅 기능을 효과적으로 살펴 볼 수 있도록 하기 위
                     해 프로그램의 로직이나 내용은 무시하고 작성한 코드이다. 만약 Visual C++ 6.0을 사용
                     한다면 stdio.h 헤더파일을 include해야 한다.

                     소스 중간에 별도의 블럭으로 구분한 부분은 동일한 이름을 갖는 변수가 지역변수(Local
                     Variable)와 전역변수(Global Variable)로 사용되었을 때 디버깅 윈도우에 어떻게 나타나는
                     지 확인하기 위해 작성한 코드이고, GetSumAlpha( ) 함수는 Step Into와 Step Over 기
                     능을 확인하기 위해 작성한 함수이다.




                     디버깅
                     소스코드의 작성이 완료되었으면 디버깅을 시작해 보자. 단, [Debug] - [Start] 메뉴를 실
                     행하거나 [F5] 키를 누르면 디버깅이 제대로 수행되지 않는다. 그 이유는 중단점을 설정하
                     지 않았기 때문이다. 따라서 일단 [F10] 키(Step Over)나 [F11] 키(Step Into)를 눌러 디버
                     깅을 시작하도록 한다. 그러면 [그림 11-9]처럼 디버깅 모드로 전환된 통합개발환경을 볼
                     수 있다(실제 화면은 독자의 Visual C++ 환경에 따라 조금씩 다를 수 있다).


         그림 11-9
  디버깅중인 Visual C++
       통합개발환경
                                                                                 151


                 에디터 왼쪽을 보면 노란 화살표(⇨)가 보일 것이다. 이 화살표가 가리키는 부분이 바로
                 현재 디버깅이 진행되고 있는 지점이다. 디버깅을 지금 시작했으므로 화살표가 _tmain( )
                 함수의 시작 위치에 있다.

                 여기서 [F10] 키를 눌러보자. 그러면 [그림 11-10]처럼 _tmain( ) 함수 내에 변수가 선언
                 된 부분을 건너뛰고, 변수 i에 10이라는 값을 지정하는 코드로 제어가 이동하는 것을 볼
                 수 있다.


     그림 11-10
[F10] 키를 눌러 다음
  단계로 이동한 모습




                 변수의 선언문 등은 실제로 기능을 수행하는 것이 아니기 때문에 디버깅 대상이 되지 않는
                 다. 이번에는 [Debug] - [Windows] 메뉴를 선택하여 Autos, Locals 윈도우를 실행해 보
                 자. [그림 11-11]은 탭으로 구분된 Autos 윈도우와 Locals 윈도우이다.


      그림 11-11
   Autos 윈도우와
    Locals 윈도우




                 Autos 윈도우를 보면 현재 디버깅중인 위치(i = 10;)에 있는 변수와 이전 위치(변수 선언문)
                 에 있는 변수들이 나타나는 것을 볼 수 있다. 아직 변수의 값이 초기화되지 않은 상태이므
                 로 Value에는 의미없는 값(Garbage)이 저장되어 있다.

                 Locals 윈도우는 Autos 윈도우에 나타난 변수와 함께 _tmain( ) 함수의 인자인 argc와
                 argv도 나타나 있다. 이것은 argc, argv도 지역 변수로 간주되기 때문이다. 또 argv 인자
152

                     는 확장 버튼(+)이 있어 세부 내용을 확인해 볼 수 있으며, argc는 프로그램으로 전달되는
                     인자의 개수를 나타내기 때문에 1이 저장되어 있다.

                     계속해서 [F10] 키를 눌러 다음 문장(j = 20;)으로 이동한다. 그러면 [그림 11-12]처럼
                     Autos 윈도우와 Locals 윈도우의 i 변수 값이 10으로 변경되는 것을 확인할 수 있다.


          그림 11-12
      i 변수 값이 10으로
            변경된다.




                     [F10] 키를 한 번 더 누른 후, 다시 Autos 윈도우와 Locals 윈도우를 확인해 보자. 디버깅
                     위치는 k = 30;으로 이동했고, Autos 윈도우에는 변수 중 k와 j만 남아있는 것을 확인할 수
                     있다. Autos 윈도우에는 현재 디버깅중인 위치(k = 30;)와 이전 위치(j = 20;)에서 사용된 변
                     수만 나타나기 때문이다. 이에 비해 Locals 윈도우에는 모든 변수의 목록이 나타난다.


          그림 11-13
  변화된 Autos 윈도우와
       Locals 윈도우




                     [F10] 키를 한 번 더 눌러보자. 예상했겠지만, Autos 윈도우에는 변수 k와 x가 나타난다.


          그림 11-14
          목록이 바뀐
         Autos 윈도우
                                                                                  153


                   이와 같이 Autos 윈도우는 디버깅 위치와 이전 위치에 사용된 변수만 보여주므로, 변수가
                   많이 선언되어 있어 목록을 확인하기 어려운 경우에 유용하다.

                   이제 Watch 윈도우를 실행해 보자. 처음에는 [그림 11-15]처럼 빈 목록이 나타난다.


       그림 11-15
     Watch 윈도우




                   Name 항목의 빈 부분을 클릭하고, i를 입력한 다음, [Enter] 키를 누른다. 계속해서 다음
                   줄에 i+1을 입력하고, [Enter] 키를 누른다. 그러면 [그림 11-16]처럼 현재 i의 값과 i에 1
                   을 더한 값이 표시된다.


       그림 11-16
Watch 윈도우에 I 값과
 I+1 값을 나타낸 화면




                   이처럼 Watch 윈도우를 이용하면 변수와 수식의 값을 검사해 볼 수 있다. [F10] 키를 계
                   속 눌러 소스에서 중간에 블럭으로 구분한 곳 중 i = 50; 위치로 이동해 보자. 그러면
                   Autos 윈도우는 [그림 11-17]처럼 나타날 것이다.


       그림 11-17
 i=50; 위치로 이동한
       Watch 윈도우
154

                      즉, 현재의 위치(i = 50;)에 사용된 변수 i와 이전 위치에 사용된 변수 s2가 목록에 나타난
                      다. 블럭 내의 변수 선언문 int i와 블럭 괄호( { ) 자체는 실행 가능한 코드가 아니기 때문
                      에 이전 위치에 해당되지 않는다.

                      이것은 소스가 컴파일되어 기계어로 만들어지는 과정을 생각하면 쉽게 이해할 수 있다. 소
                      스 코드가 기계어로 컴파일되면, 실제 실행문은 같은 값의 기계어 코드로 변환되지만, 변
                      수의 선언은 단순히 메모리를 할당하는 과정이므로 실행코드에 존재하지 않고, 데이터 영
                      역에 해당 변수의 선언 내용이 자리잡게 된다. 따라서 디버깅 단계에는 포함되지 않는다.
                      또한 기계어로 변환되면 모든 소스 코드는 주소를 갖게 되고 이 주소를 통해 제어가 이루
                      어지므로, 블럭을 구분하는 { 표시는 기계어 코드에서는 모두 제거된다. 따라서 변수의 선
                      언 내용과 마찬가지로 디버깅 단계에 포함되지 않는다.

                      [F10] 키를 한 번 더 눌러 s3 = i + ::i; 문장으로 이동해 보자. 이 문장은 전역 변수 i와 지
                      역 변수 i가 모두 사용되고 있다. 이런 경우에는 Autos 윈도우가 [그림 11-18]과 같이 나
                      타난다.


         그림 11-18
      s3=i+::i;문장으로
        이동했을 때의
        Watch 윈도우




                      반면에 Locals 윈도우는 이런 변수들을 구분하지 않는다(그림 11-19).


         그림 11-19
 Locals 윈도우는 변수를
       구분하지 않는다.




                      이처럼 같은 변수라도 디버깅 윈도우에 따라 조금씩 다르게 표시된다는 점을 유의해야 한
                      다. [F10] 키를 몇 번 더 눌러 GetSumAlpha( ) 함수를 호출하는 부분으로 이동해 보자.
                                                                                    155



     그림 11-20
   GetSumAlpha( )
   함수 호출 위치




                    여기서 [F10] 키, 즉 Step Over 기능을 선택했을 때, 디버깅 GetSumAlpha( ) 함수의 내
                    부로 이동하지 않고, 함수를 호출한 이후로 제어가 이동한다.


     그림 11-21
[F10] 키를 누른 경우




                    이 때 Autos 윈도우를 보면, [그림 11-22]처럼 GetSumAlpha( ) 함수의 반환 값을 표시해
                    준다는 것을 알 수 있다.


     그림 11-22
     Autos 윈도우
156

                       [Shift] - [F5] 키를 눌러 디버깅을 종료하고, 다시 [F10] 키를 눌러 GetSumAlpha( ) 함수
                       의 호출 위치까지 디버깅을 진행한 후, [F11] 키를 눌러보자. 그러면 [그림 11-23]처럼
                       GetSumAlpha( ) 함수 내부로 이동하여 디버깅을 계속한다.


           그림 11-23
      [F11] 키를 누른 경우




                       [F10]을 누르면 디버깅이 계속될 것이다. 만약 함수의 끝 부분까지 가기 전에 return하고
                       싶다면 [Shift] - [F11] 키(Step Out)를 눌러 함수를 빠져나오도록 한다.




                       이제 중단점을 설정해보자. 디버깅을 종료하고, [그림 11-24]처럼 GetSumAlpha( ) 함수
                       호출 이전의 위치에 중단점을 설정한다.


           그림 11-24
        중단점 설정 화면




                       [Debug] - [Start] 메뉴를 실행하거나 [F5] 기를 눌러 디버깅을 시작해 보자. 그러면 [그
                       림 11-25]처럼 중단점 위치까지 실행된 후 멈추는 것을 확인할 수 있다.
                                                                                         157



     그림 11-25
중단점에서 디버깅
작업이 중단된 화면




                    여기서 다시 [F10]이나 [F11] 키를 누르면 디버깅 작업을 계속할 수 있다. 따라서 이 기능
                    은 이미 검증이 끝난 부분을 건너뛰고자 할 때 유용하다. 또 중단점은 여러 개를 설정할
                    수도 있는데, 중단점이 여러 개이면 [F5] 키를 누를 때마다 다음 중단점까지 실행한 후 멈
                    추게 된다. 따라서 중단점을 적절히 이용하면 긴 분량의 소스 코드도 편리하게 디버깅할
                    수 있다.

                    마지막으로 Edit and Continue 기능을 확인해 보자. 디버깅을 다시 시작하고, s1 = i + j;
                    위치까지 디버깅을 실행한다.


     그림 11-26
Edit and Continue
      기능 테스트




                    그런 다음, s1 = i + j; 코드를 s1 = i + j + 1;로 수정하고 [F10] 키를 누른다. 그러면 [그림
                    11-27]처럼 변경된 내용을 반영한 후 계속해서 디버깅할 것인지를 묻는 대화상자가 나타
                    난다.
158


           그림 11-27
      Edit and Continue
         기능 적용 확인




                          여기서 [Yes]를 선택한다. 그러면 변경된 내용이 반영되고, 그 결과로 변수 s1에는 i + j의
                          값이 아니라 i + j + 1이라는 새로운 값이 저장된다. [그림 11-28]은 Autos 윈도우에서 변
                          수 s1의 값을 확인한 화면이다.


           그림 11-28
  Autos 윈도우에서 변수
  s1의 값을 확인한 화면




                          이처럼 Edit and Continue 기능은 디버깅 도중 소스를 간단히 수정하고 계속해서 디버깅
                          할 경우에 유용하며, 결과적으로 디버깅에 소요되는 시간을 단축할 수 있는 장점이 있다.
                          단, 수정해야 할 코드가 많으면 이 기능을 이용하는 것보다는 디버깅을 중단하고 소스를
                          수정한 후에 다시 디버깅하는 것이 좋다.




                          지금까지 Visual C++의 디버깅 기능에 대해 살펴보았다. 프로그램의 규모가 커지고 복잡
                          도가 증가할수록 디버깅에 소요되는 시간도 증가한다. 따라서 디버깅을 얼마나 효과적으
                          로 할 수 있는지의 여부가 오류를 빨리 해결하는 중요한 요소가 된다. 하지만 디버깅을 잘
                          하는 것보다는 처음부터 철저한 분석을 통해 논리적 오류가 최소화되도록 프로그래밍하는
                          것이 더 중요하다.
     Visual C++실전 프로그래밍
          Part-윈도우 프로그래밍 기본

             출판일 2003.1.2
           펴낸곳 디지털디자인
         제작/판매 (주)와이즈북토피아

All rights are reserved. Produced in Korea.
 No part of this book may be reproduced
in any form without permission in writing
             from the publisher.

				
DOCUMENT INFO
Shared By:
Tags: Visual
Stats:
views:30
posted:3/24/2013
language:Korean
pages:159