// 기본 Windows 헤더 파일 포함
#include <windows.h>
#include <windowsx.h>
// WindowProc 함수 프로토타입
LRESULT 콜백 WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
// 모든 Windows 프로그램의 진입점
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// 함수로 채워진 창의 핸들
HWND hWnd;
// 이 구조체는 창 클래스에 대한 정보를 보관합니다
WNDCLASSEX wc;
// 사용을 위해 창 클래스를 지웁니다
ZeroMemory(&wc, sizeof(WNDCLASSEX));
// 필요한 정보로 구조체를 채웁니다
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";
// 창 클래스 등록
RegisterClassEx(&wc);
// 창을 생성하고 그 결과를 핸들로 사용
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // 창 클래스 이름
L"Our First Windowed Program", // 창 제목
WS_OVERLAPPEDWINDOW, // 창 스타일
300, // 창의 x 위치
300, // 창의 y 위치
500, // 창 너비
400, // 창 높이
NULL, // 부모 창이 없음, NULL
NULL, // 메뉴를 사용하지 않음, NULL
hInstance, // 애플리케이션 핸들
NULL); // 여러 창과 함께 사용, NULL
// 화면에 창 표시
ShowWindow(hWnd, nCmdShow);
// 메인 루프 시작:
// 이 구조체는 Windows 이벤트 메시지를 보관합니다
MSG msg;
// 큐에서 다음 메시지를 기다리고 결과를 'msg'에 저장합니다
while(GetMessage(&msg, NULL, 0, 0))
{
// 키 입력 메시지를 올바른 형식으로 변환합니다
TranslateMessage(&msg);
// 메시지를 WindowProc 함수로 보냅니다.
DispatchMessage(&msg);
}
// WM_QUIT 메시지의 이 부분을 Windows로 반환합니다.
return msg.wParam;
}
// 이것은 프로그램의 주요 메시지 핸들러입니다.
LRESULT 콜백 WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// 정렬하여 주어진 메시지에 대해 실행할 코드를 찾습니다.
switch(message)
{
// 이 메시지는 창이 닫힐 때 읽힙니다.
case WM_DESTROY:
{
// 응용 프로그램을 완전히 닫습니다.
PostQuitMessage(0);
return 0;
} break;
}
// switch 문에서
반환하지 않은 모든 메시지를 처리합니다. DefWindowProc (hWnd, message, wParam, lParam);
}
먼저 전체 코드이다.
이 코드를 따라서 실행해보면

이러한 화면이 나올 것 이다.
위 프로그램의 코드 중에서 창을 만드는 데 사용되는 단계는 세 단계뿐이고, 나머지는 창을 계속 실행하는 데 사용됩니다. 세 단계는 다음과 같습니다.
1. 창 클래스를 등록합니다.
2. 창을 만듭니다.
3. 창을 표시합니다.
변수와 매개변수를 초기화하지 않으면 실제로는 다음과 같이 요약할 수 있다.
RegisterClassEx();
CreateWindowEx();
ShowWindow();
1. Window 클래스 등록
간단히 말해서, 윈도우 클래스는 Windows에서 다양한 윈도우의 속성과 동작을 처리하는 데 사용되는 기본적인 구조이다. 우리는 이것의 세부 사항에 대해 걱정하지 않겠지만, 정의상 C++ '클래스'가 아니라는 것을 알아야 합니다. 기본적으로 윈도우 클래스는 윈도우의 특정 속성에 대한 일종의 템플릿입니다. 이 다이어그램을 보자

이 다이어그램에서 "윈도우 클래스 1"은 "윈도우 1"과 "윈도우 2"의 기본 속성을 정의하는 데 사용되고, "윈도우 클래스 2"는 "윈도우 3"과 "윈도우 4"에 대해 동일한 작업을 수행하는 데 사용된다. 각 창에는 창 크기, 위치, 내용 등과 같은 고유한 속성이 있지만 기본 속성은 여전히 창 클래스의 속성이다.
이 단계에서는 윈도우 클래스를 등록합니다. 즉, Windows에 제공한 데이터를 기반으로 윈도우 클래스를 만들라고 지시하는 것입니다. 이를 위해 프로그램에 다음 코드가 있다.
// 이 구조체는 창 클래스에 대한 정보를 보관합니다
WNDCLASSEX wc;
// 사용할 창 클래스를 지웁니다
ZeroMemory(&wc, sizeof(WNDCLASSEX));
// 필요한 정보로 구조체를 채웁니다
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";
// 창 클래스를 등록합니다
RegisterClassEx(&wc);
WNDCLASSEX wc;
이것은 윈도우 클래스에 대한 정보를 담고 있는 구조체이다.
ZeroMemory( &wc, sizeof(WNDCLASSEX) );
ZeroMemory는 전체 메모리 블록을 NULL로 초기화하는 함수이다. 첫 번째 매개변수에 제공된 주소는 블록이 시작될 위치를 설정하고, 두 번째 매개변수는 블록의 길이를 나타낸다. 구조체 'wc'의 주소와 크기를 사용하면 전체를 빠르게 NULL로 초기화할 수 있다. 이 튜토리얼에서 다루지 않은 'wc'의 값을 초기화하는 데 사용
wc.cbSize = sizeof(WNDCLASSEX);
이 구조를 크기 조정하고 측정값을 알려줘야 합니다. sizeof() 연산자로 이를 수행합니다.
wc.style = CS_HREDRAW | CS_VREDRAW;
이 멤버에서 우리는 창의 스타일을 저장합니다. 삽입할 수 있는 값은 많지만, 게임 프로그래밍에서는 거의 사용하지 않습니다. 지금은 CS_HREDRAW를 사용하고 CS_VREDRAW와 논리적으로 OR합니다. 이 두 가지가 하는 일은 수직 또는 수평으로 이동하면 창을 다시 그리라고 Windows에 알리는 것입니다. 이것은 창에는 유용하지만 게임에는 유용하지 않습니다. 전체 화면 게임을 할 때 나중에 이 값을 재설정합니다.
wc.lpfnWndProc = WindowProc;
이 값은 윈도우 클래스에 Windows에서 메시지를 받을 때 사용할 함수를 알려줍니다.
wc.hInstance = hInstance;
애플리케이션의 복사본에 대한 핸들입니다. Windows가 우리에게 건넨 값을 WinMain()에 넣기만 하면 됩니다.
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
이 멤버는 윈도우 클래스의 기본 마우스 이미지를 저장합니다. 이는 두 개의 매개변수가 있는 LoadCursor() 함수의 반환 값을 사용하여 수행됩니다. 첫 번째는 포인터 그래픽을 저장하는 애플리케이션의 hInstance입니다. 여기서는 다루지 않으므로 NULL로 설정합니다. 두 번째는 기본 마우스 포인터를 포함하는 값입니다.
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
여기서는 배경 색상을 나타내는 데 사용됩니다.
wc.lpszClassName = L"WindowClass1";
이것은 우리가 빌드하는 윈도우 클래스의 이름입니다. 우리는 하나의 클래스만 빌드할 것이지만, "WindowClass1"이라고 이름을 붙입니다. 윈도우 자체를 만들 때 올바르게 지정하기만 하면, 어떤 이름을 붙이든 상관없습니다.
문자열 앞에 나타나는 'L'은 단순히 컴파일러에게 이 문자열이 일반적인 8비트 ANSI 문자가 아닌 16비트 유니코드 문자로 구성되어야 함을 알려줍니다.
RegisterClassEx(&wc);
윈도우 클래스를 등록합니다. 우리는 그 단일 매개변수를 우리가 함께 넣은 구조체의 주소로 채우고 Windows는 나머지를 처리합니다.
2. 창 만들기
창 클래스를 만들었으므로 해당 클래스를 기반으로 창을 만들 수 있습니다. 창은 하나만 필요하므로 전혀 복잡하지 않습니다.
// 창을 만들고 그 결과를 핸들로 사용
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // 창 클래스 이름
L"Our First Windowed Program", // 창 제목
WS_OVERLAPPEDWINDOW, // 창 스타일
300, // 창의 x 위치
300, // 창의 y 위치
500, // 창의 너비
400, // 창의 높이
NULL, // 부모 창이 없음, NULL
NULL, // 메뉴를 사용하지 않음, NULL
hInstance, // 애플리케이션 핸들
NULL); // 여러 창과 함께 사용됨, NULL
프로토타입은 이러하다.
HWND CreateWindowEx(DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam);
DWORD dwExStyle,
이 첫 번째 매개변수는 RegisterClass()가 확장된 RegisterClassEx()로 업그레이드되었을 때 추가되었습니다. 이것은 네 번째 매개변수인 dwStyle의 확장이며, 단순히 창 스타일을 위한 더 많은 옵션을 추가합니다. 이를 나타내기 위해 이 값을 NULL로 설정합니다.
LPCTSTR lpClassName,
이것은 우리 창이 사용할 클래스의 이름입니다. 클래스가 하나뿐이므로, 우리가 가진 L"WindowClass1"을 사용할 것입니다. 이 문자열은 또한 16비트 유니코드 문자를 사용하므로, 앞에 'L'을 붙입니다.
LPCTSTR lpWindowName,
이것은 창의 이름이며 창의 제목 표시줄에 표시됩니다. 이것은 또한 유니코드를 사용합니다.
DWORD dwStyle,
여기서 우리는 창에 대한 모든 종류의 옵션을 정의할 수 있습니다. 최소화 및 최대화 버튼을 제거하고, 크기를 조정할 수 없게 만들고, 스크롤 막대를 만들고 다양한 것을 컨트롤 할 수 있다.
int x,
화면의 x축을 따라 창의 위치를 결정합니다.
int y,
화면의 y축을 따라 창의 위치를 결정합니다.
int nWidth,
창의 초기 너비를 설정합니다.
int nHeight
창의 초기 높이를 설정합니다.
HWND hWndParent,
이것은 우리가 지금 만들고 있는 창을 어떤 부모 창이 만들었는지 Windows에 알려주는 매개변수입니다. 부모 창은 다른 창을 포함하는 창입니다. 현재 여기에는 부모 창이 없지만, 예를 들어, 같은 창에서 여러 문서를 열 수 있는 Microsoft Word는 여러 개의 '자식' 창이 있는 부모 창으로 구성되어 있습니다.
우리는 이런 일을 전혀 하지 않으므로 이 매개변수를 NULL로 설정할 것입니다.
HMENU hMenu,
메뉴 바의 핸들입니다. 우리는 메뉴 바도 없으므로 이것도 NULL입니다.
HINSTANCE hInstance,
인스턴스의 핸들입니다. hInstance로 설정하세요.
LPVOID lpParam
이것은 우리가 여러 개의 창을 만들 때 사용할 매개변수입니다. 우리는 그렇지 않기 때문에 간단하게 유지하고 NULL로 선언
Return Value
이 함수의 반환 값은 Windows가 이 새 창에 할당할 핸들입니다. 우리는 이것을 다음과 같이 hWnd 변수에 직접 저장합니다.
hWnd = CreateWindowEx(NULL,
...
이게 윈도우를 만드는 전부이며, 이제 마지막 단계가 있다. 사용자에게 윈도우를 보여줘야 합니다.
3. 창을 보이기
두 개의 매개변수가 있는 하나의 함수가 필요합니다.
BOOL ShowWindow(HWND hWnd,
int nCmdShow);
HWND hWnd,
이건 우리가 방금 만든 창에 대한 핸들일 뿐이므로, 여기에 Windows가 우리에게 돌려준 값을 넣습니다.
int nCmdShow
전체화면으로 나타나게 하면 상관없다.
창을 만들었습니다. 하지만 실행되진 않을 것입니다. 창은 이것보다 더 많은 것을 필요로 합니다. 그래서 이제 프로그램의 다음 섹션인 WinProc() 함수와 메시지 루프로 넘어갑니다.
Windows 이벤트 및 메시지 처리
창을 만든 후에는 창을 계속 작동시켜서 상호 작용할 수 있도록 해야 합니다. 물론 지금처럼 프로그램을 종료하면 컴파일조차 되지 않습니다. 하지만 컴파일이 되었다고 가정하더라도 WinMain()의 끝에 도달하면 창이 생성되고 파괴되는 플래시만 볼 수 있습니다.
종료하는 대신, 우리 프로그램은 창 생성을 완료하고 메인 루프라고 불리는 것으로 시작합니다. 앞서 말했듯이, Windows 프로그래밍은 이벤트에 기반합니다. 즉, Windows가 우리에게 명령을 내릴 때만 우리 창은 무언가를 해야 합니다. 그렇지 않으면 그냥 기다립니다.
Windows가 우리에게 메시지를 전달하면 여러 가지 일이 즉시 발생합니다. 메시지는 이벤트 큐에 배치됩니다. GetMessage() 함수를 사용하여 큐에서 해당 메시지를 검색하고, TranslateMessage() 함수를 사용하여 특정 메시지 형식을 처리하고, DispatchMessage() 함수를 WindowProc() 함수에 사용하여 메시지의 결과로 실행할 코드를 선택합니다.
아래와 같은 형태로 돌아간다.

필요 여ㅇㄴㅇㄴ부에 관계없이 nCmdShow에서 얻은 값을 여기에 넣고 그대로 둡니다.
이벤트 처리가 프로그램의 나머지 절반을 구성합니다. 이벤트 처리 자체는 두 부분으로 나뉩니다.
1. 메인 루프
2. WindowProc() 함수
메인 루프는 GetMessage(), TranslateMessage(), DispatchMessage() 함수로만 구성되어 있습니다. WindowProc() 함수는 특정 메시지가 전송될 때 실행되는 코드로만 구성되어 있습니다. 놀랍게도 간단합니다. 지금 파고들어서 알아보겠습니다.
1. 메인 루프
위의 다이어그램에서 볼 수 있듯이, 이 섹션은 세 가지 함수로만 구성되어 있습니다. 각 함수는 실제로 매우 간단하므로 너무 자세히 설명할 필요는 없지만 간단히 다루겠습니다.
메인 루프에 필요한 코드는 다음과 같습니다.
// 이 구조체는 Windows 이벤트 메시지를 보관합니다.
MSG msg;
// 큐에서 다음 메시지를 기다리고 결과를 'msg'에 저장합니다.
while(GetMessage(&msg, NULL, 0, 0))
{
// 키 입력 메시지를 올바른 형식으로 변환합니다.
TranslateMessage(&msg);
// 메시지를 WindowProc 함수로 보냅니다.
DispatchMessage(&msg);
}
MSG msg;
MSG는 단일 이벤트 메시지에 대한 모든 데이터를 포함하는 구조체입니다. 일반적으로 이 구조체의 내용에 직접 액세스할 필요는 없지만, 액세스할 경우 포함된 내용을 살펴보겠습니다.
while(GetMessage(&msg, NULL, 0, 0))
GetMessage()는 메시지 큐에서 모든 메시지를 꺼내 msg 구조체로 가져오는 함수입니다. 프로그램이 종료되려고 할 때를 제외하고는 항상 TRUE를 반환하며, 이 경우에는 FALSE를 반환합니다. 이런 방식으로 while() 루프는 프로그램이 완전히 종료될 때만 끊어집니다.
GetMessage() 함수에는 다루어야 할 매개변수가 네 개 있지만 먼저 프로토타입을 살펴보겠습니다.
BOOL GetMessage(LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax);
LPMSG lpMsg,
이 매개변수는 방금 다룬 것과 같은 메시지 구조체에 대한 포인터입니다. 우리는 여기에 구조체의 주소만 넣을 것입니다.
HWND hWnd,
이것은 메시지가 나와야 할 창의 핸들입니다. 그러나 여기에 NULL을 넣었다는 것을 알 수 있습니다. 이 매개변수에서 NULL은 모든 창에 대한 다음 메시지를 가져오는 것을 의미합니다. 실제로 여기에 hWnd 값을 넣을 수 있으며 아무런 차이가 없지만 여러 창이 있는 경우에는 차이가 있습니다. 이 매개변수에 NULL을 넣었습니다.
UINT wMsgFilterMin, UINT wMsgFilterMax
이러한 매개변수는 메시지 큐에서 검색할 메시지 유형을 제한하는 데 사용할 수 있습니다. 예: wMsgFilterMin에서 WM_KEYFIRST를 사용하고 WM_KEYLAST를 사용하면 메시지 유형이 키보드 메시지로 제한됩니다. WM_KEYFIRST와 WM_KEYLAST는 첫 번째와 마지막 키보드 메시지의 정수 값과 동의어입니다. 마찬가지로 WM_MOUSEFIRST와 WM_MOUSELAST는 메시지 유형을 마우스 메시지로 제한합니다.
이러한 매개변수에는 특별한 경우가 하나 있습니다. 각 값을 '0'으로 채우면 GetMessage()는 값에 관계없이 모든 메시지를 수집하려고 한다고 가정합니다. 우리 프로그램에서 이렇게 했다는 것을 알 수 있을 것입니다.
TranslateMessage(&msg);
TranslateMessage()는 특정 키 입력을 적절한 형식으로 변환하는 함수입니다. 모든 것이 자동으로 수행되며, 단일 매개변수는 msg 구조체의 주소로 채워집니다.
DispatchMessage(&msg);
DispatchMessage()는 기본적으로 그 말대로 합니다. 메시지를 전송합니다. 다음에 다룰 WindowProc() 함수로 전송합니다. 이 함수는 msg 구조체의 주소로 채워진 단일 매개변수도 가지고 있습니다.
2. WindowProc() 함수
이제 메인 루프를 살펴보았으니, 계속 언급되고 있는 WindowProc() 함수에 대해 알아보겠습니다.
다음은 무슨 일이 일어나는지에 대한 간략한 개요입니다. GetMessage()는 메시지를 받고, 우리는 그것을 번역하고, DispatchMessage() 함수를 사용하여 그것을 전송합니다.
DispatchMessage() 함수는 그런 다음 적절한 WindowProc() 함수를 호출합니다. 다행히도 걱정해야 할 WindowProc() 함수가 하나뿐이므로 비교적 간단할 것입니다.
WindowProc() 함수가 호출되면 MSG 구조체에서 네 가지 정보가 전달됩니다. 이를 확인하려면 WindowProc()의 프로토타입을 살펴보겠습니다.
LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
메시지가 WindowProc에 들어오면, 우리는 uMsg 인수를 사용하여 그것이 어떤 메시지인지 확인할 수 있습니다. 많은 프로그래머는 switch() 문을 사용하여 메시지를 확인합니다. 많은 프로그래머는 switch() 문을 사용하여 메시지를 확인합니다. 다음은 이를 수행하는 방법의 예입니다.
// 메시지를 정렬하여 실행할 코드를 찾습니다.
switch(message)
{
// 이 메시지는 창이 닫힐 때 읽힙니다.
case WM_DESTROY:
{
// ...
// ...
} break;
}
여기서 switch 문은 실행할 코드를 찾습니다. 물론, WM_DESTROY 메시지만 제공하므로 다른 메시지가 전송되면 그냥 무시합니다. WM_DESTROY 메시지는 창이 닫힐 때 전송됩니다. 따라서 창이 닫히면 애플리케이션을 정리하기 위해 필요한 모든 작업을 수행할 수 있습니다. 지금은 애플리케이션에 모든 작업이 완료되었음을 알리고 모든 작업이 정리되었음을 나타내는 '0'을 반환하는 것으로 구성됩니다. 다음 코드로 이를 수행합니다.
case WM_DESTROY:
{
// 애플리케이션을 완전히 닫습니다
PostQuitMessage(0);
return 0;
} break;
PostQuitMessage() 함수는 정수 값 '0'을 갖는 메시지 WM_QUIT를 보냅니다. 메인 루프로 돌아가 보면 GetMessage() 함수는 프로그램이 종료되려고 할 때만 FALSE를 반환한다는 것을 기억할 것입니다. 좀 더 구체적으로 말하면, WM_QUIT의 경우처럼 '0'이 전송되면 FALSE를 반환합니다. 따라서 이 함수가 기본적으로 하는 일은 프로그램에 WinMain() 함수를 제대로 끝내라고 말하는 것입니다.
그 다음 우리는 '0'을 반환합니다. 이것은 Windows에 우리가 메시지를 처리했다는 것을 알려주기 때문에 매우 중요합니다. 만약 우리가 다른 것을 반환했다면 Windows는 혼란스러울 수 있습니다.
우리 프로그램의 코드를 공부하면 설명할 마지막 함수가 하나 있는데, DefWindowProc()입니다. 이 함수가 하는 일은 우리가 처리하지 않은 메시지를 처리하는 것입니다. 간단히 말해서, '0'을 반환하지 않은 메시지를 처리합니다. 따라서 WindowProc() 함수의 끝에 두어 우리가 놓친 것을 잡도록 합니다.
'DirectX11' 카테고리의 다른 글
DirectX11 - Tutorial 5 (실시간 메시지 루프) (0) | 2024.08.03 |
---|---|
DirectX11 - Tutorial 4 (창 크기, 클라이언트 크기) (0) | 2024.08.03 |
DirectX11 - Tutorial 2 (Hello World) (0) | 2024.08.03 |
DirectX11 - Tutorial 1 (동작 순서) (0) | 2024.08.03 |