어소트락 게임 아카데미 Win32 API 정주행 1회차 - main.cpp

2023. 5. 18. 23:15Win32 API

Win32 API를 시작하기 위해 프로젝트를 만들면 기존에 C++에서 배웠던 main함수는 없고 엄청 어려워 보이는 처음보는 키워드들이 보인다. 하지만 복잡할 것은 전혀 없고 알고보면 어이없을 정도로 별 거 없다.

 

1. wWinMain 함수의 매개변수

1) SAL (Soursecode Annotation Language)

강의에서 다뤘던 것은 SAL 문법이라는 것이었고 최신 C++은 이런 역할을 하는 키워드들을 문법적으로 제공한다. 우선 아래의 코드를 보자. 이 함수는 메인 함수와 같은 역할을 하는 녀석으로 Visual Studio에서 프로젝트 생성을 할 때, Windows 데스크톱 애플리케이션으로 생성할 경우에 나타나는 프로그램의 첫 진입점 역할을 하는 녀석이다.

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine, 
                      _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    
    /* 이하 생략 */
}

 

보면 알겠지만 _In_이나 _In_opt_ 와 같은 쌩뚱맞는 매크로와 처음 보는 자료형들도 자주 보인다. 이제부터 이 녀석들을 하나하나 뜯어볼 계획이다.

 

C++을 거의 처음 배울 때로 기억을 되돌려 보자. 보통 주석을 언제 달까? 왜 달아야 할까? 코드에서 어떤 부분에 주석을 달지 몰라도 일단 주석을 달았다면 그 부분에 대한 어떤 부가 설명이 반드시 필요하다는 뜻일 거다. 그게 뭐가 되었든 어떤 의도를 가지고 있든 코드로만 표현될 수 없지만 알려줄 무언가가 존재할 때 우리는 주석을 이용한다. 지금 위에 있는 코드를 보면 UNREFERENCED_PARAMETER라는 매크로 함수에 hPrevInstance와 lpCmdLine이라는 변수를 넣고 있는데 사실 이것은 두 변수가 이 함수에서 사용되지 않는다는 것을 표현하기 위한 것이다.

 

그럼 여기서 위에 있는 코드를 아래와 같이 바꿨을 때를 생각해 보자.

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine, 
                      _In_ int nCmdShow)
{
    // hPrevInstance는 이 함수에서 사용되지 않습니다.
    // lpCmdLine는 이 함수에서 사용되지 않습니다.
    
    /* 이하 생략 */
}

 

이렇게 코드를 작성한다면 어떻게 될까? 당연히 의미 전달은 된다. 실제로 프로젝트를 생성하면 Microsoft에서 제공한 wWinMain의 예제를 보면 실제로 hPrevInstance는 사용되지 않고 있다. 그런 부분에 대해서 지금 코드와 이전 코드를 비교했을 때, 기능만 놓고 본다면 어떤 차이도 없다. 그러나 잘 생각해보자. 저런 상황이 올 때마다 주석을 만드는 것이 너무 불편할 것 같지 않은가? 너무 불편하게 주석을 달아야 하는 상황이 과연 저런 상황뿐일까? 그리고 주석은 저렇게 달아 놨지만 누군가가 실수로 저 함수 내부에서 해당 변수를 사용하면 어떻게 될까? 이런 문제를 해결하기 위해 나온 것이 SAL 주석이다. 기능상으로 아무런 차이가 없지만 불편함을 해결하는 것이다. 아래와 같이 말이다.

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine, 
                      _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    
    /* 이하 생략 */
}

 

그런데 문제가 하나 있다. 위의 코드에 아래와 같이 코드 한 줄을 추가해보자.

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine, 
                      _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    
    hPrevInstance = hInstance; // 악! 내 눈!!
    
    /* 이하 생략 */
}

 

hPrevInstance를 사용하지 않는다고 해놓고 아무렇지 않게 사용하고 있다. 문제는 누군가가 실수라도 이렇게 하면 코드를 직접 전부 들여다 보고 눈으로 잡아내지 못하면 이 실수를 어느 누구도 알아챌 방법이 없다는 것이다. 컴파일러가 잡아주지도 않을 뿐더러 애초에 UNREFERENCED_PARAMETER라는 매크로 함수가 표준 문법이 아니어서 다른 컴파일러에서 돌아가지 않는 코드가 된다. C++에서 이 문제를 해결하기 위해 표준 문법으로 제공하는 것들이 있다.

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 
                      [[deprecated]] _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine, 
                      _In_ int nCmdShow)
{
    // UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    
    hPrevInstance = hInstance; // 응~ 이제 안돼~
    
    /* 이하 생략 */
}

 

처음 보는 문법이 등장했다. [[ ]] 라는 부분인데 C++에서 이것을 attribute라고 한다. 직역하면 속성으로 hPrevInstance에 어떤 속성을 부여하는 것으로 deprecated라는 속성을 부여하면 그 뜻에 맞게 더 이상 hPrevInstance라는 변수는 사용될 수 없는 것이다. attribute라는 기능 자체는 C++ 11부터 지원하는 기능이라고 하며 deprecated는 C++ 14부터 지원한다고 한다. 본인의 컴파일러가 C++ 버전 몇까지 지원하는지 잘 살펴볼 필요가 있을 것이다.

 

사용되지 않는 변수는 애초에 받아오지 않는 것이 정상이지만 오래된 코드에서 어떤 함수를 사용하는데 그 함수의 매개변수 리스트와 같은 구조를 변경하기 어려울 때, 사용되지 않는 변수를 매개변수 리스트에서 제거할 수 없을 때, 사용되는 문법이라고 할 수 있다. C++은 다른 attribute도 제공하며 필요에 따라 적절한 것을 사용해야 한다. 사실 상황에 따라서 지금 예제 코드에서 사용된 attribute가 적절하지 않는 것일 수도 있기 때문에 이건 본인이 직접 잘 판단해서 attribute를 결정해야 할 문제이다.

 

지금까지 진행된 내용을 이해했다면 _In_, _in_opt_와 같은 녀석들도 어떤 역할을 하며 어떤 의도를 가지고 있는지 조금만 검색하면 알아낼 수 있을 것이다. _In_은 input의 약자로 어떤 함수의 인자같은 곳에 입력하는 용도로만 사용한다는 뜻이며 _in_opt_는 _in_과 같이 쓰이겠지만 _opt_가 option 즉, 선택 사항에 대한 것으로 사용되지 않아도 무방하다는 뜻이다. 이 정도만 알아놓고 넘어가도 무지에서 오는 알지 못하는 키워드에 대한 껄끄러움은 말끔하게 해결될 것이다.

 

아래의 링크는 C++의 attribute에 대한 자세한 내용이 있으니 필요할 때 참고하면 좋다.

https://wikidocs.net/51417

 

01.03.03. 속성(Attributes)

[TOC] 속성(attributes)는 C\++11부터 지원되는 기능으로써 자료형, 객체, 코드 등에 대하여 정의된 실행 속성을 지정 한다. ```{.no-highlight} …

wikidocs.net

 

2) Win32 API 자료형

이제 처음보는 매크로들이 무슨 역할을 하는지는 다 알았다. 근데 아직 해결되지 않은 것이 있으니... 바로 처음보는 자료형들이다. 그 중에서 HINSTANCE라는 자료형에 대해 알아볼 것이다.

 

우리가 프로그램을 만들어서 실행을 하면 그것은 메모리에 적재가 된다. 이것을 프로세스라고 부른다는 것은 컴퓨터 구조, 운영체제에 대해서 조금이라도 배운 적이 있다면 머릿속 어딘가에 이렇게 부른다는 것 정도는 들어있을 것이다. 여기서 프로세스가 메모리에 올라가 있다는 것은 곧 그 주소가 존재한다는 뜻으로 받아들일 수 있다. HINSTANCE는 메인 함수가 시작될 때 전달되는 매개변수로 프로그램의 시작 주소를 의미한다. 즉, hInstance의 의미가 프로그램의 시작 주소라는 뜻이다.

 

그럼 hPrevInstance는 뭘까? 아래의 그림을 보자.

필자의 컴퓨터 화면이다.

보다시피 그림판이라는 같은 프로그램이 2개가 켜져있다. 옛날의 윈도우는 hPrevInstance에 여러 개의 프로세스가 메모리에 올라올 것을 알고 이전의 프로세스에 대한 주소를 전달해주고 있었다. 즉, hPrevInstance라는 변수 앞에 더 이상 이 변수가 사용되지 않는다는 SAL이 뜬금없이 붙어있던 이유가 바로 이것이다. 과거의 잔재로 과거에 존재했던 구조를 함부로 변경하면 거기에 드는 비용이 많이 들어서 그런 키워드를 사용한 것이다. 다시 말해서 시스템이 바뀌면서 hPrevInstance가 더 이상 사용되지 않는 변수가 되어버린 것이다.

 

여기부터 정말 신기한 것이 있는데 방금 말한 시스템이 바뀐 것에 대한 내용이다. hInstance의 값을 막상 출력하면 1번 그림판이든 2번 그림판이든 그 값이 동일하게 나온다는 것이다. 이게 뭔 소리냐? 아래의 그림을 보면서 이해해야 하는데 그 전에 윈도우는 가상 메모리라는 시스템을 쓴다는 것을 기억해야 한다. 이제부터 실제 메모리를 바다 전체, 가상 메모리를 무인도라고 비유하고 설명할 것이다.

2개의 무인도와 2명의 사람이 있다.

실제 메모리라는 넓은 바다에는 프로세스가 쓰는 메모리 시스템 즉, 가상 메모리라는 2개의 무인도가 있다. 각각의 무인도에는 각자의 중심점이 있을 것이고 무인도에 있는 사람의 입장에서 보면 그 곳이 이 세상의 중심일 수 밖에 없는 것이다. 넓은 바다의 입장에서 보면 완전히 다른 위치에 있는데 각자 똑같이 중심에 있다고 말하는 것처럼 같은 프로그램이 2개가 실행되면 각자 다른 메모리에 적재가 되지만 실제로 hInstance를 출력하면 가상 메모리에서 봤을 때, 같은 위치가 나오기 때문에 같은 값이 출력되는 것이다.

 

3) 커맨드

지금까지 hInstance, hPrevInstance에 대해서 알아봤다. 이제 2개의 매개변수가 남았는데 얘네들은 우리가 cmd창을 열어서 명령어를 실행시키는 것과 관련이 있는 매개변수들이다. 혹시 cmd를 열어서 어떤 명령어를 입력해본 적이 있는가? 그 중 몇몇 입력들은 입력이 되었을 때, 어떤 정보들을 마구 출력해주는 것도 볼 수 있다. 근데 이게 사실은 윈도우에서 이미 제공해주는 프로그램을 실행시키는 것이다. 만약 cmd로 명령어를 입력해본 경험이 좀 있다면 그 과정에서 어떤 속성을 같이 지정할 수 있다는 것도 알고 있을 것이다.

 

LPWSTR은 그런 명령어를 받아오는 매개변수로 내부를 들여다보면 wchar_t* 라는 자료형으로 typedef이 걸려있는 것을 확인할 수 있다. 즉, 우리가 지금부터 Win32 API를 이용해서 게임 엔진을 만들건데 cmd 명령으로 게임 엔진을 실행시키고 싶다면 사용하면 되고 아니면 안쓰면 된다. nCmdShow 역시 그냥 플래그로 별 거 없다. 실제로 여기에서 nCmdShow에 대한 설명을 구글 번역기에 돌리면 다음과 같은 설명이 나온다.

 

nCmdShow는 기본 응용 프로그램 창이 최소화, 최대화 또는 정상적으로 표시되는지 여부를 나타내는 플래그입니다.

 

MSDN 링크

https://learn.microsoft.com/en-us/windows/win32/learnwin32/winmain--the-application-entry-point

 

The WinMain application entry point - Win32 apps

Learn about the function named WinMain or wWinMain that every Windows program includes, and its parameters.

learn.microsoft.com

 

wWinMain 앞에 있는 APIENTRY도 역시 마찬가지로 SAL과 같은 맥락으로 __stdcall로 되어있는데 사실상 사용자인 우리의 입장에서 이런 SAL을 굳이 사용하지는 않는다.

 

 

2. 전역변수

Visual Studio에서 Windows 데스트톱 애플리케이션으로 프로젝트를 만들면 다음과 같은 전역변수들을 볼 수 있다.

HINSTANCE hInst;                                // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.

 

hInst는 1-2에서 설명했으니 생략하고 szTitle과 szWindowClass에 대해서 설명해보겠다. 이미 주석에도 나와있는 것처럼 우리가 프로그램을 실행했을 때 나오는 창의 타이틀 이름을 의미하는 문자열이다. 아래의 이미지를 보자.

바로 빨갛게 어그로를 끌고 있는 저 부분에 대한 문자열이 szTitle안에 있는 문자열이다. 아래의 소스 코드를 보면 저 함수가 실행될 때, szTitle에 있는 문자열이 전달되어서 우리가 초기에 설정한 프로젝트의 이름이 자동으로 저곳에 표시되는 것이다. 다시 말하면 우리가 원하는 다른 문자열을 아래의 함수에 직접 입력하면 창에 뜨는 타이틀의 이름이 바뀐다는 것이다. 이건 본인이 직접 해보자.

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

    // 바로 이 부분에 의해 창이 만들어지는데 여기 있는 szTitle이 타이틀 이름이다.
    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

    if (!hWnd)
    {
        return FALSE;
    }

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

    GWindowInfo.hwnd = hWnd;

    return TRUE;
}

 

근데 여기서 의문점이 하나 든다. 우리는 szTitle에 프로젝트 이름을 입력한 적이 없다. 근데 어떻게 szTitle안에 우리가 만든 프로젝트의 이름이 전달되었던 걸까? 그것은 바로 wWinMain에 있는 이 부분 때문이다.

// 바로 이 녀석이 szTitle에 문자열을 넣는다. 근데 어떻게 넣을까?
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);

 

어떻게 szTitle에 문자열을 넣어주는지 확인하기 위해 단축키 Ctrl + Shift + E를 눌러보자. 그럼 리소스 뷰라는 것이 뜨는데 여기서 StringTable이라는 것을 볼 수 있다. 그걸 보면 모든 의문이 풀릴 것이다. 실제로 거기에 있는 문자열이 LoadString 함수에 의해 szTitle에 전달되는 것이며 StringTable을 우리가 편집하는 것도 가능하고 그걸 편집해서 우리가 띄울 창의 이름을 다른 이름으로 바꾸는 것도 가능하다. 어쨋든 결론은 szTitle이라는 전역 변수는 창의 타이틀 이름을 의미하는 것이며 CreateWindowW라는 함수에 들어가는 문자열 인자가 창의 타이틀 이름으로 만들어진다는 것이다.

 

실제로 StringTable을 보면 알겠지만 초기에 생성되는 Resource.h에 있는 수많은 매크로들과 다양한 숫자들 중에서 아까 봤던 StringTable에 있는 숫자들을 볼 수 있을 것이다. 사실 Resource.h는 문자열과 같은 프로그램을 실행하는데 있어서 필요한 자원에 대한 데이터를 #define으로 숫자로 묶어놓은 헤더파일이다. 이제 아래의 두 이미지를 비교해보면 뭐가 뭔지 명확하게 보이게 될 것이다. 또한 IDD_ABOUT_BOX는 같은 숫자가 있더라고 전혀 다른 카테고리의 리소스로 숫자가 겹쳐도 상관이 없다.

 

지금까지 szTitle에 대해서 알아봤는데 여기까지 이해했다면 szClassWindow에 값이 매겨지는 원리도 이와 똑같다는 것말 알고 있으면 된다. szClassWindow는 우리가 창을 띄우는데 있어서 필요한 정보를 불러오는데 사용할 키 값으로 사용한다. 프로젝트를 생성했을 때, 기본적으로 만들어지는 함수 중에서 MyRegisterClass를 보면 창을 띄우는데 필요한 정보를 초기화하고 그 정보들에 대한 키 값을 szClassWindow로 세팅하는 것을 볼 수 있다. 이후에 InitInstance 함수에서 창을 생성할 때, szClassWindow를 키로 전달해서 창을 생성하는 방식인 것이다.

 

일례로 아래에 있는 코드를 다음과 같이 바꾸면 창을 생성하는데 필요한 정보들 중 메뉴바에 대한 정보를 "메뉴바를 사용하지 않겠다" 로 바꿨기 때문에 이 상태로 실행하면 메뉴바가 나타나지 않는다는 것을 볼 수 있다.

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEAMCREATOR));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = nullptr; // <- 이 부분을 이렇게 변경한다.
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

메뉴가 없는 것을 볼 수 있다.

 

 

3. 메인 루프

메인 함수 내부에서 InitInstance까지 봤으니 이제 그 다음 줄을 볼 차례이다. 내가 만들 게임 엔진의 루프는 3가지 단계로 구성된다. 우선 무한 루프를 돌면서 입력이 들어왔는지 검사한 후에 들어온 입력을 받은 채로 매 루프마다 실행할 연산들을 처리한다. 마지막으로 렌더링 기능을 통해서 이전 단계에 이루어졌던 연산을 근거로 원하는 위치에 렌더링을 해주면 된다. 우선 아래의 코드를 보자.

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine,
                      _In_ int nCmdShow)
{
    // 생략
    
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEAMCREATOR));

    MSG msg;
    
    while (GetMessage(&msg, nullptr, 0, 0, PM_REMOVE))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int)msg.wParam;
}

봐야 할 코드는 LoadAccelerators라는 함수를 호출하는 부분이다. Accelerator는 직역하면 어떤 기계를 잘 작동시키기 위한 장치라고 생각하면 된다. 즉, 여기서의 Accelerator는 외부에서 들어오는 사용자의 입력을 말하며 키보드 단축키에 대한 정보가 저기에서 로드된다. 이 역시 리소스 뷰에서 Accelerator라는 폴더의 테이블을 통해서 확인할 수 있으며 만약 프로그램에서 키보드 입력을 사용하지 않는다면 없어도 상관이 없다.

 

MSG는 message를 뜻하며 GetMessage에서 외부에서 들어온 메세지를 저장한 메세지 큐를 통해 어떤 메세지를 받았는지 msg에 전달하게 된다. 이 메세지에는 외부의 입력만 있는 것이 아니라 화면에 그림을 그리는 그리기 명령과 같은 것들이 포함되어 있다. 그리고 TranslateAccelerator에서 본인의 프로세스에 있는 어떤 창에서 메세지를 받으려 하고 있는지 hwnd를 통해 검사를 한다. 만약 어떤 창에서 입력을 처리하려고 한다면 TranslateMessage와 DispatchMessage라는 함수를 통해서 입력을 처리하며 아래의 코드를 통해서 전달된 함수가 호출된다.

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc; // <- 이 부분을 주목하자.
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEAMCREATOR));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = nullptr;
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

 

아까 이 함수는 프로그램을 실행하는 데 있어서 창을 띄우는데 필요한 정보를 세팅한다고 했었다. 그 중에서 lpfnWndProc라는 변수에 WndProc라는 함수를 함수 포인터에 바인딩을 시켜서 어떤 입력이 들어왔을 때, 그 함수를 자동으로 호출해주는 것이다. 애초에 위 코드에서 Proc가 Procedure를 의미하는 것임을 유추하는 것이 가능하다면 함수를 바인딩시키는 것이라는 것을 쉽게 알 수 있다.

 

이제 여기서 WinProc라는 함수를 보면 switch case문이 있는 것을 볼 수 있다. 이는 사용자가 원하는 메세지의 형태를 switch의 case로 만들고 원하는 형태로 구현한 이후에 나머지 메세지 형태에 대한 경우는 default로 처리해서 윈도우에서 제공하는 DefWindowProc를 통해 메세지를 받은 것을 버리는 연산을 하는 형태로 작동하게 되는 것이다. 기본으로 제공되는 함수의 형태를 보면 알겠지만 이 함수에서 사용자 입력에 대한 처리와 렌더링에 대한 처리를 어디서 해야 하는지 대충 보일 것이다.

 

 

번외

아까 전에 메세지 큐에서 메세지를 받아오는 함수를 GetMessage라고 했었다. 근데 게임 엔진을 만들어야 하는 상황에서 GetMessage는 아주 큰 약점을 하나 가지고 있다. 그것은 GetMessage가 메세지를 처리하는데 있어서 작동하는 방식 때문이다. 사실 이 함수는 메세지 큐로부터 메세지를 받아올 때까지 함수 내부에서 대기 상태로 멈춰있는다. 상상해보자. 게임을 하다가 해당 게임에 어떤 메세지도 들어오지 않는 상황이 되었다고 하자. 예를 들어서 방치형 게임을 만든다던가 할 때, 어떤 입력도 들어오지 않아서 멈춰야 한다면 어떻게 될 것 같은가?

 

따라서 GetMessage가 아니라 다른 함수로 입력을 처리해야 하거나 루프문의 구조를 조금 바꿔야 하는데 이 방법은 나중에 다루도록 하겠다... 라고는 했지만 사실 Rookiss의 DX12를 보고 PeekMessage 함수에 대해 알고 있는 사람이라면 방법을 알테니 다음 기회에 자세히 다뤄보자.