Thinking Different





Tutorial 2 - 프레임워크와 윈도우 만들기


원문 : http://www.rastertek.com/dx11s2tut02.html




DirectX 11로 코드를 작성하기 전에 간단한 코드 프레임 워크를 작성하는 것이 좋습니다. 차후에 작성될 대량의 효과적인 코드 확장 및 관리를 위해서는 프레임워크를 작성하여 클래스 구조로 뼈대를 세우는것이 가장 좋은 방법입니다.



프레임 워크


프레임 작업은 네 가지 항목으로 시작됩니다. 응용 프로그램의 진입 점을 처리하는 WinMain 함수가 있습니다. 또한 WinMain 함수 내에서 호출 될 전체 응용 프로그램을 캡슐화하는 시스템 클래스가 있습니다. 시스템 클래스 안에는 사용자 입력을 처리하기위한 입력 클래스와 DirectX 그래픽 코드를 처리하기위한 그래픽 클래스가 있습니다. 다음은 프레임 워크 설정 다이어그램입니다.



이제 프레임 워크가 어떻게 설정 될지 살펴 보았습니다.



우선 전방선언 사용의 이점과 빌드 시간 감축을 위해서 미리 컴파일된 헤더(stdafx.h)의 이점을 사용하기 위해서 미리 사용될 헤더파일을 따로 작성 'DxDefine.h' 하여 만들도록 하겠습니다.


Stdafx.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// stdafx.h : 자주 사용하지만 자주 변경되지는 않는
// 표준 시스템 포함 파일 또는 프로젝트 관련 포함 파일이
// 들어 있는 포함 파일입니다.
//
 
#pragma once
 
#include "targetver.h"
 
#define WIN32_LEAN_AND_MEAN             // 거의 사용되지 않는 내용은 Windows 헤더에서 제외합니다.
// Windows 헤더 파일:
#include <windows.h>
 
// C 런타임 헤더 파일입니다.
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
 
 
// TODO: 프로그램에 필요한 추가 헤더는 여기에서 참조합니다.
#include "DxDefine.h"


22 번째 코드 한줄을 추가합니다. 다음은 DxDefine.h 파일의 내용을 살펴보겠습니다.



DxDefine.h



지금은 내용이 없지만 다음 편 부터는 directx sdk를 사용하기 위한 헤더 및 라이브러리 파일 코드를 넣을 것입니다.



다음은 main.cpp 파일의 WinMain 함수를 살펴 보겠습니다.


WinMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Dx11Demo_02.cpp: 응용 프로그램의 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include "SystemClass.h"
 
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine,
                        _In_ int nCmdShow)
{
    // System 객체 생성
    SystemClass* System = new SystemClass;
    if (!System)
    {
        return -1;
    }
    
    // System 객체 초기화 및 실행
    if (System->Initialize())
    { 
        System->Run();
    }
 
    // System 객체 종료 및 메모리 반환
    System->Shutdown();
    delete System;
    System = nullptr;
 
    return 0;
}



보시다시피 WinMain 함수는 매우 간단하게 유지했습니다. 시스템 클래스 생성, 초기화, 실행, 종료, 반환 순서로 구동됩니다. 따라서 우리는 매우 단순하게 유지하고 전체 애플리케이션을 시스템 클래스 내에 캡슐화했습니다.


이제 시스템 클래스 헤더 파일을 살펴 보겠습니다.



SystemClass.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#pragma once
 
 
class InputClass;
class GraphicsClass;
 
 
class SystemClass
{
public:
    SystemClass();
    SystemClass(const SystemClass&);
    ~SystemClass();
 
    bool Initialize();
    void Shutdown();
    void Run();
 
    LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
 
private:
    bool Frame();
    void InitializeWindows(int&, int&);
    void ShutdownWindows();
 
private:
    LPCWSTR m_applicationName;
    HINSTANCE m_hinstance;
    HWND m_hwnd;
 
    InputClass* m_Input = nullptr;
    GraphicsClass* m_Graphics = nullptr;
};
 
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static SystemClass* ApplicationHandle = 0;



클래스의 정의는 매우 간단합니다. 여기에 정의 된 WinMain에서 호출 된 Initialize, Shutdown 및 Run 함수가 표시됩니다. 또한 이러한 함수 내에서 호출되는 일부 개인 함수가 있습니다. 우리는 클래스에 MessageHandler 함수를 두어 응용 프로그램이 실행되는 동안 응용 프로그램에 전송 될 Windows 시스템 메시지를 처리합니다. 마지막으로 우리는 그래픽과 입력을 처리 할 두 객체에 대한 포인터가 될 m_Input과 m_Graphics라는 클래스 변수를 가지고 있습니다. 또한 전방 선언을 통해 빠른 컴파일을 작성할 수 있도록 하였습니다.

간단히 설명하자면 SystemClass는 WInAPI 윈도우 프로그래밍을 클래스화 한 것입니다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#include "stdafx.h"
#include "inputclass.h"
#include "graphicsclass.h"
#include "systemclass.h"
 
 
SystemClass::SystemClass()
{
}
 
 
SystemClass::SystemClass(const SystemClass& other)
{
}
 
 
SystemClass::~SystemClass()
{
}
 
 
bool SystemClass::Initialize()
{
    // 윈도우 창 가로, 세로 넓이 변수 초기화
    int screenWidth = 0;
    int screenHeight = 0;
 
    // 윈도우 생성 초기화
    InitializeWindows(screenWidth, screenHeight);
 
    // m_Input 객체 생성. 이 클래스는 추후 사용자의 키보드 입력 처리에 사용됩니다.
    m_Input = new InputClass;
    if(!m_Input)
    {
        return false;
    }
 
    // m_Input 객체 초기화
    m_Input->Initialize();
 
    // m_Graphics 객체 생성.  그래픽 랜더링을 처리하기 위한 객체입니다.
    m_Graphics = new GraphicsClass;
    if(!m_Graphics)
    {
        return false;
    }
 
    // m_Graphics 객체 초기화.
    return m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
}
 
 
void SystemClass::Shutdown()
{
    // m_Graphics 객체 반환
    if(m_Graphics)
    {
        m_Graphics->Shutdown();
        delete m_Graphics;
        m_Graphics = 0;
    }
 
    // m_Input 객체 반환
    if(m_Input)
    {
        delete m_Input;
        m_Input = 0;
    }
 
    // Window 종료 처리
    ShutdownWindows();
}
 
 
void SystemClass::Run()
{
    // 메시지 구조체 생성 및 초기화
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
    
    // 사용자로부터 종료 메시지를 받을때까지 메시지루프를 돕니다
    while(true)
    {
        // 윈도우 메시지를 처리합니다
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            // 종료 메시지를 받을 경우 메시지 루프를 탈출합니다
            if (msg.message == WM_QUIT)
                break;
 
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            // 그 외에는 Frame 함수를 처리합니다.
            if (!Frame())
                break;
        }
    }
}
 
 
bool SystemClass::Frame()
{
    // ESC 키 감지 및 종료 여부를 처리합니다
    if(m_Input->IsKeyDown(VK_ESCAPE))
    {
        return false;
    }
 
    // 그래픽 객체의 Frame을 처리합니다
    return m_Graphics->Frame();
}
 
 
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
    switch(umsg)
    {
        // 키보드가 눌러졌는가 처리
        case WM_KEYDOWN:
        {
            // 키 눌림 flag를 m_Input 객체에 처리하도록 합니다
            m_Input->KeyDown((unsigned int)wparam);
            return 0;
        }
 
        // 키보드가 떨어졌는가 처리
        case WM_KEYUP:
        {
            // 키 해제 flag를 m_Input 객체에 처리하도록 합니다.
            m_Input->KeyUp((unsigned int)wparam);
            return 0;
        }
 
        // 그 외의 모든 메시지들은 기본 메시지 처리로 넘깁니다.
        default:
        {
            return DefWindowProc(hwnd, umsg, wparam, lparam);
        }
    }
}
 
 
void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
    // 외부 포인터를 이 객체로 지정합니다
    ApplicationHandle = this;
 
    // 이 프로그램의 인스턴스를 가져옵니다
    m_hinstance = GetModuleHandle(NULL);
 
    // 프로그램 이름을 지정합니다
    m_applicationName = L"Dx11Demo_02";
 
    // windows 클래스를 아래와 같이 설정합니다.
    WNDCLASSEX wc;
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = m_hinstance;
    wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO);
    wc.hIconSm       = wc.hIcon;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = m_applicationName;
    wc.cbSize        = sizeof(WNDCLASSEX);
    
    // windows class를 등록합니다
    RegisterClassEx(&wc);
 
    // 모니터 화면의 해상도를 읽어옵니다
    screenWidth  = GetSystemMetrics(SM_CXSCREEN);
    screenHeight = GetSystemMetrics(SM_CYSCREEN);
 
    int posX = 0;
    int posY = 0;
 
    // FULL_SCREEN 변수 값에 따라 화면을 설정합니다.
    if(FULL_SCREEN)
    {
        // 풀스크린 모드로 지정했다면 모니터 화면 해상도를 데스크톱 해상도로 지정하고 색상을 32bit로 지정합니다.
        DEVMODE dmScreenSettings;
        memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
        dmScreenSettings.dmSize       = sizeof(dmScreenSettings);
        dmScreenSettings.dmPelsWidth  = (unsigned long)screenWidth;
        dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight;
        dmScreenSettings.dmBitsPerPel = 32;            
        dmScreenSettings.dmFields     = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
 
        // 풀스크린으로 디스플레이 설정을 변경합니다.
        ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
    }
    else
    {
        // 윈도우 모드의 경우 800 * 600 크기를 지정합니다.
        screenWidth  = 800;
        screenHeight = 600;
 
        // 윈도우 창을 가로, 세로의 정 가운데 오도록 합니다.
        posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth)  / 2;
        posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2;
    }
 
    // 윈도우를 생성하고 핸들을 구합니다.
    m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName, 
                            WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP,
                            posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL);
 
    // 윈도우를 화면에 표시하고 포커스를 지정합니다
    ShowWindow(m_hwnd, SW_SHOW);
    SetForegroundWindow(m_hwnd);
    SetFocus(m_hwnd);
}
 
 
void SystemClass::ShutdownWindows()
{
    // 풀스크린 모드였다면 디스플레이 설정을 초기화합니다.
    if(FULL_SCREEN)
    {
        ChangeDisplaySettings(NULL, 0);
    }
 
    // 창을 제거합니다
    DestroyWindow(m_hwnd);
    m_hwnd = NULL;
 
    // 프로그램 인스턴스를 제거합니다
    UnregisterClass(m_applicationName, m_hinstance);
    m_hinstance = NULL;
 
    // 외부포인터 참조를 초기화합니다
    ApplicationHandle = NULL;
}
 
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
    switch(umessage)
    {
        // 윈도우 종료를 확인합니다
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
 
        // 윈도우가 닫히는지 확인합니다
        case WM_CLOSE:
        {
            PostQuitMessage(0);        
            return 0;
        }
 
        // 그 외의 모든 메시지들은 시스템 클래스의 메시지 처리로 넘깁니다.
        default:
        {
            return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
        }
    }
}




튜토리얼을 간결하게 유지하기 위해 DirectInput Tutorial을 사용할 때까지 당분간 윈도우 입력을 사용하도록 하겠습니다. 입력 클래스는 키보드의 사용자 입력을 처리합니다. 이 클래스는 SystemClass :: MessageHandler 함수로부터 입력을받습니다. 입력 객체는 키보드 배열에 각 키의 상태를 저장합니다. 질의를 받으면 특정 키가 눌려 졌는지를 호출 함수에 알려줍니다. 다음은 헤더입니다.



InputClass.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
 
class InputClass
{
public:
    InputClass();
    InputClass(const InputClass&);
    ~InputClass();
 
    void Initialize();
 
    void KeyDown(unsigned int);
    void KeyUp(unsigned int);
 
    bool IsKeyDown(unsigned int);
 
private:
    bool m_keys[256];
};



InputClass.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "stdafx.h"
#include "inputclass.h"
 
 
InputClass::InputClass()
{
}
 
 
InputClass::InputClass(const InputClass& other)
{
}
 
 
InputClass::~InputClass()
{
}
 
 
void InputClass::Initialize()
{
    // 키 배열을 초기화합니다
    for(int i=0; i<256; ++i)
    {
        m_keys[i] = false;
    }
}
 
 
void InputClass::KeyDown(unsigned int input)
{
    // 키가 눌렸다면 해당 키값을 true로 저장합니다
    m_keys[input] = true;
}
 
 
void InputClass::KeyUp(unsigned int input)
{
    // 키가 해제되었다면 해당 키값을 false로 저장합니다
    m_keys[input] = false;
}
 
 
bool InputClass::IsKeyDown(unsigned int key)
{
    // 현재 키값이 눌려졌는지 아닌지 상태를 반환합니다
    return m_keys[key];
}



그래픽 클래스는 시스템 클래스에 의해 생성되는 다른 객체입니다. 이 응용 프로그램의 모든 그래픽 기능은이 클래스에 캡슐화됩니다. 또한 이 파일의 헤더를 전체 화면 또는 창 모드와 같이 변경할 수있는 모든 그래픽 관련 전역 설정에 사용합니다. 현재 이 클래스는 비어 있지만 나중에 튜토리얼에는 모든 그래픽 객체가 포함됩니다.


GraphicsClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma once
 
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
 
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();
 
    bool Initialize(intint, HWND);
    void Shutdown();
    bool Frame();
 
private:
    bool Render();
};



GraphicsClass.cpp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "stdafx.h"
#include "graphicsclass.h"
 
 
GraphicsClass::GraphicsClass()
{
}
 
 
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
 
 
GraphicsClass::~GraphicsClass()
{
}
 
 
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    return true;
}
 
 
void GraphicsClass::Shutdown()
{
}
 
 
bool GraphicsClass::Frame()
{
    return true;
}
 
 
bool GraphicsClass::Render()
{
    return true;
}



출력 화면




마치면서


이제 우리는 기본 프레임 워크를 작성하였고 그 프래임워크를 바탕으로 화면에 윈도우 창을 띄울수 있게 되었습니다. 이 프레임워크는 향후 모든 듀토리얼의 기반이 될 것이므로이 프레임워크를 이해하는 것이 매우 중요합니다. 다음 튜토리얼로 넘어 가기 전에 아래 연습문제를 수행하여 코드가 컴파일되고 작동하는지 확인하십시오. 이 프레임워크를 이해하지 못하더라도 자신감을 가지고 다음 듀토리얼로 넘어가는 것이 좋습니다. 다음 듀토리얼들을 진행하면서 프레임워크를 조금씩 작성하며 배우다보면 나중에 좀더 이해할 수 있을 것입니다.



연습문제


graphicsclass.h 헤더에서 FULL_SCREEN 매개 변수를 true로 변경 한 다음 프로그램을 다시 컴파일하고 실행하십시오. 창을 표시 한 후에 종료하려면 ESC 키를 누릅니다.



소스코드


소스코드 : Dx11Demo_02.zip