앞서 2. WinMain에서 윈도우 OS는 윈도우(창)과 메세지 이렇게 두 가지가 핵심적인 역할을 한다고 언급한 바 있다.
그 중 이번에는 메세지를 자세히 살펴보자.
윈도우는 특정 사건이 발생했을 때(ex.클릭) 메세지를 보내 그에 맞는 행동을 실행하는 방식의 아키텍처이다. 이렇게 보면 message driven같기도 한데, 또 어떤 면에서는 (백그라운드에서 자원을 관리한다던지) event driven 같기도 하고, 사실 윈도우OS정도 되는 어마어마한 프로그램은 굳이 방식을 구분짓는 게 무의미하다고 생각이 들긴 한다.
아무튼 중요한 건 메세지를 보내 사건을 처리한다는 점이다.
윈도우에서 사건이 발생되면 다음과 같은 흐름으로 처리된다.
가로줄로 어플리케이션 단과 OS단을 구분지어놓은 것을 신경쓰며 살펴보자.
마우스를 움직였을 경우 윈도우의 해당 창의 메세지 큐에 마우스 움직임이 저장되고, 마우스 클릭도 마찬가지이다.
큐에 메세지가 쌓이다가, 앱단에서 준비가 되어 GetMessage() API를 호출하면 아까의 그 큐로부터 메세지를 뽑아온다.
Filter, Modify를 거쳐 메세지를 가공하고 Translate 단계에 도달하는데, 이 부분은 추후 WM_CHAR에 관한 내용을 다룰 때 다시 설명하겠다.
일단은 대략 메세지 가공의 일부라고만 이해하고 넘어가자.
그 후 DispathMessage() API를 통해 이 가공된 메세지를 Handle하기 위해 OS단으로 다시 넘긴다.
OS는 저장된 WndProc 포인터값을 이용해 넘겨받은 메세지를 해당 창의 WndProc으로 전달해 처리를 맡긴다.
(추가설명: 윈도우에서 메세지를 핸들링하는 함수를 WndProc (Window Process)라고 하는데, 우리가 이전에 WNDCLASSEX를 정의할 때 lpfnWndProc이라는 인자가 있었다는 것을 상기하자. 바로 이 인자는 해당 창(윈도우)의 WndProc 기본값을 포인터로 저장하는 인자로, 우리는 기본값인 DefWindowProc으로 저장한 것을 볼 수 있다.
이 DefWindowProc은 윈도우에서 기본적으로 제공하는 WndProc으로, 화면 드래그드랍, 창 축소화 등 수많은 기본적인 기능들에 대한 윈도우 메세지를 handling해주는 WndProc이다. 여기에 만약 우리가 추가적으로 특정 behavior를 원한다면 우리가 WndProc을 만들어 lpfnWndProc에 넘겨주면 되는 것이다. 일반적으로 우리가 재정의해줘야 하는 경우가 많다
DefWindowProc에 관한 한 가지 알아둘 사실은, 기본값으로 윈도우 창을 닫아도 프로세스가 종료되지 않는다는 점이다. 즉 그냥 기본값으로 바로 사용하면 좀비프로세스가 된다.
떄문에 대부분의 경우 창을 끄면 프로세스도 종료되길 원하기에, 이를 수정한 커스텀 WndProc을 만들어 사용해야 한다. 구현은 아래 코드를 참고하자.
)
메세지는 그렇게 WndProc에서 처리되는 것으로 윈도우 프로그램의 기본적인 흐름이 완료된다.
함수의 자세한 파라미터를 살펴보자. 사실 너무 자세히 볼 필요는 없고, MSDN에 아주 자세한 설명이 나와있으니 간략하게 짚고 넘어가겠다.
핵심 파라미터는 lpMsg로, 여기에 LPMSG 구조체 포인터를 전달해 GetMessage()를 실행시켜 얻은 결과값을 가져올 수 있다.
HWND hWnd는 메세지를 받고 싶은 창(Window)의 핸들을 넘겨줄 수 있는데, 하나의 윈도우 프로그램이 여러개의 창을 지닐 수 있다는 점을 명심하자. 어쨌든 이 값은 대체적으로 NULL을 입력하는데, NULL을 전달시 해당 프로세스가 보유한 모든 창에서의 메세지를 수신하기 때문이다. (즉 특정 창에서의 이벤트만 처리하고싶은 게 아니면 NULL로 해놓으면 된다) wMsgFilterMin, Max는 받고 싶은 메세지의 종류를 필터링할 수 있는 값들로 자주 사용할 일은 없으니 자세한건 MSDN을 참고하자. 필터 없애려면(일반적인 경우) 0,0으로 설정하면 된다.
반환값은 다음과 같다.
이번에는 메세지 구조체를 살펴보자. (이름은 tagMSG라고 나와있는데 이게 GetMessage의 LPMSG 인자라고 생각하면 된다)
여기서 중요한 것만 보면,
hwnd: 마찬가지로 해당 메세지와 관련된 창 핸들 전달,
message: 가장 중요한 값으로 해당 메세지가 어떤 종류인지를 INT로 나타낸다,
wParam과 lParam: 메세지 종류마다 의미하는 값이 다른데(Context-dependent), 그냥 다양한 용도로 사용되는 파라미터이다.
정도가 있다.
실제로 사용해보자.
#include <Windows.h>
LRESULT CALLBACK WndProc( HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam )
{
switch( msg )
{
case WM_CLOSE:
PostQuitMessage( 555 ); // 창을 닫으면 프로세스 종료와 함께 555가 전달되도록 만들었다
// PostQuitMessage는 이름 그대로 WM_QUIT을 전송하며, 위에서 첫 인자로 전달된 555는 메세지의 wParam에
break;
}
return DefWindowProc( hWnd,msg,wParam,lParam );
}
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
{
const auto pClassName = "hw3dbutts";
// register window class
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof( wc );
wc.style = CS_OWNDC;
wc.lpfnWndProc = WndProc; // ----> Custom WndProc으로 바꾼 것을 볼 수 있다
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = pClassName;
wc.hIconSm = nullptr;
RegisterClassEx( &wc );
// create window instance
HWND hWnd = CreateWindowEx(
0,pClassName,
"Happy Hard Window",
WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
200,200,640,480,
nullptr,nullptr,hInstance,nullptr
);
// show the damn window
ShowWindow( hWnd,SW_SHOW );
// message pump
MSG msg;
BOOL gResult;
while( (gResult = GetMessage( &msg,nullptr,0,0 )) > 0 )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
if( gResult == -1 )
{
return -1;
}
else
{
return msg.wParam; // 555가 전달될
}
}
실행해서 창을 닫아보면 프로세스가 정상적으로 종료됨을 확인할 수 있다.
'computer graphics > DirectX' 카테고리의 다른 글
[D3D Engine] 6. WM_CHAR, Mouse (0) | 2023.03.19 |
---|---|
[D3D Engine] 5. Window Messages (1) | 2023.03.19 |
[D3D Engine] 3. Window Creation (0) | 2023.03.19 |
[D3D Engine] 2. WinMain (0) | 2023.03.19 |
[D3D Engine] 1. Introduction (0) | 2023.03.19 |