본문 바로가기
게임 공부/Windows API

윈도우의 메시지 처리 과정

by woohyeon 2020. 4. 2.
반응형

개인적으로 공부하는 내용이므로 틀린 부분이 있을 수 있습니다. 있다면 알려주세요.


[Windows OS의 윈도우 프로그램]
 Windows 운영체제의 윈도우 프로그램은 메시지 기반으로 동작한다. 여기서 메시지란 마우스의 클릭, 키보드의 입력, 윈도우의 이동 및 변화 등이 값 형태로 존재하는 것을 말한다. 윈도우 프로그램은 이러한 메시지들을 하나씩 가져와서 그에 맞게 구현된 루틴대로 작업을 수행한다.

이러한 메시지들은 일종의 메시지 큐라는 곳에 순서대로 저장이 된다. 운영체제는 하나의 시스템 메시지 큐를 가지고, 이와 별개로 각 스레드 당 1개의 메시지 큐를 가질 수 있다. 메시지 종류는 크게 두 가지로 나뉘는데 큐를 거쳐서 프로그램으로 전달되는 큐(Queued) 메시지가 있고, 큐에 적재되지 않고 곧바로 프로그램으로 전달되는 비큐(Nonqueued) 메시지가 있다. 모든 큐 메시지들은 먼저 시스템 메시지 큐에 저장이 된다. 참고로 마우스 또는 키보드와 같은 하드웨어에 의한 입출력 데이터는 디바이스 드라이버에 의해 메시지로 변환되어 저장된다.  

메시지가 큐에 적재되는 과정

 이렇게 시스템 메시지 큐에 저장된 메시지들은 운영체제의 백그라운드 프로그램에 의해 각각의 스레드 메시지 큐로 전송되고, 전송된 메시지들은 시스템 메시지 큐에서 제거된다. 각 스레드로 알맞게 전송할 수 있는 것은 메시지 안에 자신이 속한 윈도우에 대한 정보가 존재하기 때문에 가능하다.

아래 그림과 같이 하나의 스레드는 여러개의 윈도우를 가질 수 있는데 메시지의 정보를 통해 적절한 윈도우로 전송될 수 있다. 

이렇게 스레드 메시지 큐에 저장된 메시지들을 GetMessage() 또는 PeekMessage() 함수를 통해 가져온다. GetMessage()는 가져오면서 메시지 큐에서 제거를 하고, PeekMessage()는 제거할 수도 하지 않을 수도 있다. 어쨌든 가져온 메시지는  DispatchMessage()라는 함수를 통해 실질적으로 메시지를 처리하는 함수인 윈도우 프로시저(WndProc)로 전송된다.    WndProc은 사용자가 직접 구현하는 함수이지만, 사용자에 의해 호출되는 것이 아닌 시스템(OS)에 의해 호출되는 콜백 함수이다. 처음에 언급했던 비큐 메시지는 이러한 과정을 거치지 않고 바로 WndProc으로 전달되어 처리된다.

이러한 메시지 처리 과정은 계속해서 반복적으로 일어나기 때문에 루프(반복문) 형태로 구현한다. 이는 보통 윈도우 프로그램의 진입점인 WinMain()에 구현한다. 이러한 루프는 사용자가 구현하며 보통 다음과 같은 형태를 가진다. 아래는 GetMessage()를 사용하는 경우와 PeekMessage()를 사용하는 경우를 각각 보였다.

// GetMessage()
 MSG msg;
 ZeroMemory( &msg, sizeof( msg ) );
 while( GetMessage( &msg, NULL, 0, 0 ) )
 {
   TranslateMessage( &msg );
   DispatchMessage( &msg );
 }


// PeekMessage()
 MSG msg;
 ZeroMemory( &msg, sizeof( msg ) );
 while( msg.message != WM_QUIT )
 {
   if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
   {
     TranslateMessage( &msg );
     DispatchMessage( &msg );
   }
   else
   {
	   // 다른 작업
   }
 }

 

이러한 루프의 종료를 위해 WM_QUIT 라는 메시지가 존재한다. 만약 메시지 큐로부터 가져온 메시지가 WM_QUIT라면 루프를 멈추고 프로그램을 종료하라는 뜻이다. 루프의 종료 조건은 메세지를 가져오는 함수에 따라 2가지로 나뉜다.

우선 GetMessage()라는 함수는 WM_QUIT 메세지를 받으면 0을 반환한다. 그 외의 메세지를 받으면 0이 아닌 값을 반환한다. 따라서 위와 같이 간단하게 루프를 구성할 수 있다. 반면 PeekMessage()는 어떠한 메시지를 받아도 유효한 메시지면 0이 아닌 값을 반환한다. 즉 WM_QUIT이 종료를 의미하긴 하지만 메세지 자체는 유효하기 때문에 해당 메세지를 받아도 루프를 계속 진행한다. 따라서 별도로 메시지가 WM_QUIT일 경우 루프를 종료하는 조건문을 작성해주어야 한다. 

WM_QUIT이라는 메시지는 우리가 메시지 전송 함수를 통해 직접적으로 전달할 수 없는 메시지이다. 이를 위해 PostQuitMessage() 함수가 존재하며 이 함수를 호출하면 WM_QUIT이라는 메시지를 스레드 메시지 큐로 전송해준다. 또한 WM_QUIT은 시스템이 아닌 우리가 처리하는 메시지이기 때문에 WndProc으로 전달되지 않는다.  위 코드에선 whle문의 조건에서 확인 후 루프를 중지하기 때문에 WM_QUIT을 받아도 if문에 한번 들어가게 된다. 만약 이 부분이 찝찝하다면 종료 조건을 if문에서 검사해도 된다.

윈도우 프로그램을 esc 또는 Alt+F4를 통해 종료하려고 하면 시스템으로 WM_DESTROY라는 메시지가 전달되는데 보통 이를 이용하여 처리한다. WM_DESTROY는 WndProc으로 전송이 가능하기 때문에 WndProc 내에 WM_DESTROY 메시지를 받으면 PostQuitMessage() 함수를 호출하고 함수를 종료하도록 한다. 그러면 WM_QUIT 메시지가 메시지 큐에 전달되고 GetMessage() 또는 PeekMessage() 를 통해 WM_QUIT을 받아 루프를 탈출할 수 있도록 한다. 아래는 WndProc의 전형적인 예이다.

LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch( msg )
    {
        case WM_DESTROY:
            PostQuitMessage( 0 );
            return 0;
        
        ...
    }

    return DefWindowProc( hWnd, msg, wParam, lParam );
}

 참고로 DefWindowProc() 함수는 switch 문에서 처리하지 않은 나머지 메시지들을 처리해주는 함수이다.

'게임 공부 > Windows API' 카테고리의 다른 글

GetMessage()와 PeekMessage()의 차이  (0) 2020.06.22
HINSTANCE 타입과 HWND 타입의 차이  (0) 2020.06.12
4. WinMain: The application entry point  (0) 2020.03.31
3. What is a Window?  (0) 2020.03.31
2. Working with Strings  (0) 2020.03.31



댓글