이해가 안가는 것들이 많아서 추가로 개념을 정리하고 가려고 한다.
RegisterClass – 언제나 휴일 (ehpub.co.kr)
이곳에 정리가 잘 되어있어서 자료를 가져왔다.
윈도우즈 프로그램의 기본 흐름
1. 아까 입력했던것 처럼 WNDCLASS로 윈도우 프로그램을 다 설정해준다.
2. RegisterClass를 통해 윈도우 클래스를 등록한다.
3. CreateWindow를 통해 인스턴스를 생성한다.
4. ShowWindow를 통해 만든 인스턴스를 시각화하고
5. 메시지를 루프시킨다.
RegisterClass
ATOM WINAPI RegisterClass(WNDCLASS *lpWndClass);
이 함수의 원형을 찾아가보면 WNDCLASS형 포인터 변수를 매개변수로 입력해줘야 한다.
RegisterClass(&wc);
그래서 아까 WNDCLASS형 변수로 만들어주었던 wc의 주소값을 넘겨주면 됐던거다.
등록이 성공하면 0이 아닌값을 반환하고, 실패하면 0을 반환한다고 한다.
CreateWindow
HWND WINAPI CreateWindow(LPCWSTR lpClassName,//윈도우 클래스 이름
LPCWSTR lpWindowName,//윈도우 인스턴스 캡션 명(타이틀)
DWORD dwStyle, //윈도우 스타일
int X, //좌상단 x좌표
int Y, //좌상단 y좌표
int nWidth, //너비
int nHeight, //높이
HWND hWndParent, //부모 윈도우 핸들
HMENU hMenu, //메뉴 핸들
HINSTANCE hInstance, //모듈의 인스턴스 핸들
LPVOID lpParam);//생성할 때 전달할 인자
HWND hWnd = CreateWindow(wc.lpszClassName, L"First", WS_OVERLAPPEDWINDOW, 100, 100, 800, 600, NULL, NULL, hInst, NULL);
인스턴스를 생성할땐 위의 인자들을 입력 해주어야한다.
HWND 타입은 윈도우 핸들이라는 뜻으로 운영체제가 자원에 할당하는 값이다. 핸들은 프로그램 내의 1개 이상의 윈도우(창)을 식별하는 데 사용된다.
모듈의 인스턴스 핸들은 프로세스 자신의 인스턴스 핸들로 운영체제에게 누가 생성을 요청하는 것인지 구분하기 위해 전달하는 것입니다.
HWND라고 해서 정말 생소했는데 윈도우 핸들러? 개념으로 이해하면 될 것같다.
ShowWindow
ShowWindow(hWnd,nShow);//윈도우 인스턴스 시각화, SW_SHOW(시각화), SW_HIDE(비시각화)
//해당 윈도우가 화면에 보이도록
ShowWindow(hWnd, nCmd);
화면에 시각화를 하기위해 ShowWindow를 해줘야한다.
윈도우의 인스턴스를 생성할때 만든 윈도우 핸들러와 WinMain에 있는 nCmd 변수값을 넣어주었었다.
MSG
사실 이 부분이 제일 이해가 안됐다..
일단 MSG 구조체부터 살펴보자.
struct MSG {
HWND hwnd;//사건과 관련있는 윈도우 핸들
UINT message;//메시지 구분자
WPARAM wParam; //메시지 처리에 필요한 인자1
LPARAM lParam; //메시지 처리에 필요한 인자2
DWORD time; //사건이 발생한 시각
POINT pt; //사건이 발생할 때의 마우스 좌표
};
구조체 안을 들여다보니 조금 이해가 될 것 같다.
BOOL WINAPI GetMessage
일단 While의 조건에 있었던 GetMessage를 뜯어보자.
BOOL WINAPI GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax);
GetMessage함수에서는 첫 번째 인자로 전달한 주소에 메시지를 꺼내어 설정합니다. 그리고 두 번째 인자부터 네 번째 인자는 원하는 메시지를 필터링할 때 사용합니다. 특정 윈도우에 발생한 메시지만 꺼내기를 원하면 두 번째 인자를 유효한 윈도우 핸들을 전달하세요. 그리고 특정 메시지만 꺼내기를 원하면 세 번째와 네 번째 인자로 꺼내고자 하는 메시지 번호의 구간을 전달합니다.
아직 완전히 이해는 안가지만 msg의 주소를 인자값으로 넘기고 나머지는 메세지를 필터링할때 필요하다는건 이해가 됐다.
TranslateMessage & DispatchMessage
아까 날 괴롭혔던 While문을 다시 뜯어본다.
위는 퍼온 블로그 주인분의 코드고, 밑은 아까 진행한 코드다.
MSG Message ;
while(GetMessage(&Message,0,0,0))//메시지 루프에서 메시지 꺼냄(WM_QUIT이면 FALSE 반환)
{
TranslateMessage(&Message);//WM_KEYDOWN이고 키가 문자 키일 때 WM_CHAR 발생
DispatchMessage(&Message);//콜백 프로시저가 수행할 수 있게 디스패치 시킴
}
//메세지값
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
//메세지 처리 후
TranslateMessage(&msg);
DispatchMessage(&msg);
}
주의할 사항으로는 GetMessage는 메시지 큐에 메시지가 없을 때 FALSE를 반환하는 것이 아니라 WM_QUIT 메시지를 꺼낼 때 FALSE를 반환한다는 것입니다. 윈도우즈 프로그램에서 처리할 메시지가 없다고 프로세스를 종료하는 것이 아니라 종료 메시지를 수신하였을 때 종료하게 처리합니다.
그리고 프로그램 방식으로 WM_QUIT 메시지를 메시지 큐에 전달할 때는 PostQuitMessage를 호출합니다.
윈도우즈 프로그램은 최종 사용자에 의해 컨트롤이나 마우스 누름, 키 누름 등의 동작에 따라 동작합니다. 이러한 동작을 하면 운영체제는 이를 감지하여 프로세스의 응용 메시지 큐에 메시지를 전달합니다. 응용에서는 응용 메시지 큐에 도착한 메시지를 하나씩 꺼내어 콜백 프로시저에서 처리하는 구조입니다.
Win32 및 C++를 사용하는 창 메시지(시작) - Win32 apps | Microsoft Docs
위의 마소 도큐먼트에 가면 생각이 잘 정리 될 수가 있다.
정리해보자면,
1. 프로그램이 시작하면 클릭이 되거나 , 버튼이 눌리거나 하는것과 같은 메세지들이 수도없이 들어온다.
2. 그것을 받아주는게 GetMessage이다.
3. 이 메세지를 수신해서 큐에 하나하나 담고, 꺼내서 순서대로 전달 해준다.
4. TranslateMessage는 키보드 입력과 관련이 있다. 키 입력을 문자로 변환시켜 주며, DispatchMessage전에 호출해야 한다. (WM_CHAR 메세지가 TranslateMessage가 있기 때문에 전달이 된다.)
5. DispatchMessage 함수는 운영 체제에 메시지의 대상인 창의 창 프로시저를 호출하도록 지시합니다.
운영 체제는 창 테이블에서 창 핸들을 조회하고 창과 연결된 함수 포인터를 찾아 함수를 호출합니다.
즉, 메세지가 들어오면 lpfnWndProc에 할당된 함수에 메세지를 보내준다는 뜻 같다.
dispatch라는 영어 단어의 뜻만 안다면 어느정도 예상되는 함수였다.
LRESULT CALLBACK WindowProc
DispatchMessage 함수는 메시지의 대상인 창의 창 프로시저를 호출합니다. 창 프로시저에는 다음 서명이 있습니다.
이 함수는 아까 Case문을 작성했던 함수이다.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
네 가지 매개 변수가 있습니다.
hwnd 는 창에 대한 핸들입니다.
uMsg 는 메시지 코드입니다. 예를 들어 WM_SIZE 메시지는 창의 크기가 조정되었음을 나타냅니다.
wParam 및 lParam 에는 메시지와 관련된 추가 데이터가 포함되어 있습니다. 정확한 의미는 메시지 코드에 따라 달라집니다.
LRESULT는 프로그램이 Windows 반환하는 정수 값입니다. 특정 메시지에 대한 프로그램의 응답을 포함합니다. 이 값의 의미는 메시지 코드에 따라 달라집니다.
CALLBACK 은 함수에 대한 호출 규칙입니다. 일반적인 창 프로시저는 단순히 메시지 코드를 켜는 큰 switch 문입니다. 처리하려는 각 메시지에 대한 사례를 추가합니다.
첫번째 인자값으로 CreateWindow로 윈도우 인스턴스를 생성할때 만든 윈도우 핸들러 변수를 넣어주면 되고, 나머지는 DispatchMessage가 전달해주는 메세지 값 안에 들어있는 인자들이다. (MSG 구조체를 보면 알 수 있다.)
즉 , DipatchMessage함수가 lpfnWndProc에 할당된 함수를 호출하고 그 함수에 들어온 메세지 값을 보내주어 상황에 따라 Case문으로 정해둔 기능들을 callback 해주는 것 같다.
마소 도큐먼트의 예시와 아까 작성했던 코드를 한번 보자.
switch (uMsg)
{
case WM_SIZE: // Handle window resizing
// etc
}
예를 들어 WM_SIZE 메시지에 대한 설명서에는 다음이 명시됩니다.
wParam 은 창이 최소화, 최대화 또는 크기 조정되었는지 여부를 나타내는 플래그입니다.
lParam 에는 창의 새 너비와 높이가 16비트 값으로 32비트 또는 64비트 숫자 하나로 압축됩니다. 이러한 값을 얻으려면 비트 이동을 수행해야 합니다. 다행히 헤더 파일 WinDef.h에는 이 작업을 수행하는 도우미 매크로가 포함되어 있습니다.
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM Iparam)
{
switch (uMsg)
{
case WM_CLOSE:
//프로그램 종료됨
PostQuitMessage(0);
break;
case WM_PAINT:
break;
//키보드를 누르면 발생하는 메세지 (wParam으로 들어옴)
case WM_CHAR:
break;
}
return DefWindowProc(hWnd, uMsg, wParam, Iparam);
}
이제 전달받은 MSG의 UINT값(메세지 구분자)을 통해 case문으로 상황에 따른 기능을 실행 하는것은 알았다.
근데 여기서 아까 키보드 입력을 할 때 작성했던 코드를 다시 보자.
case WM_CHAR:
if (wParam == 8)
{
len--;
}
else {
str[len++] = (wchar_t)wParam;
}
//WM_Paint 메세지가 호출됨
InvalidateRect(hWnd, nullptr, true);
break;
}
입력 받은 값으로 wParam을 쓴 것을 알 수 있다.
위의 마소 도큐먼트 설명을 보면 "wParam 은 창이 최소화, 최대화 또는 크기 조정되었는지 여부를 나타내는 플래그입니다." 라고 되어있다.
이걸로 알 수 있는 사실은 전달되는 메세지마다 wParam과 LParam의 사용 기능이 다르다는 것이다.
마소에서 메세지 코드마다 wParam과 LParam이 뭘 의미하는지 와서 확인하랍니다..
이제 대충 돌아가는 큰 틀은 알 것 같다.
하지만 마지막 하나가 남았다.
return DefWindowProc
위의 코드를 보면 스위치문 바로 밑에 DefwindowProc을 리턴해주는 것을 볼 수 있다.
이게 무엇인가?
return DefWindowProc(hwnd, uMsg, wParam, lParam);
기본 메시지 처리
창 프로시저에서 특정 메시지를 처리하지 않는 경우 메시지 매개 변수를 DefWindowProc 함수에 직접 전달합니다. 이 함수는 메시지 유형에 따라 달라지는 메시지에 대한 기본 작업을 수행합니다.
마소 도큐먼트만 보고서는 이해가 살짝 안가서 다른 블로그 글들을 보고 왔다.
정리하자면 우리는 LRESULT CALLBACK WindowProc에 들어오는 메세지를 통해 case문으로 기능들을 처리 했는데,
모든 기능들을 하나하나 다 Case문안에 사람이 넣기가 힘드니 정말 기본적인 기능들은
DefWindowProc(hwnd, uMsg, wParam, lParam) 함수가 대신 해주는 것이다.
예를들면 우리가 아까 작업했던 코드로 윈도우 창을 실행해서 만져보면 윈도우의 이동이나 크기변경같은 처리는 실제로 case문에 처리해주지 않아도 가능한데 이 이유가 DefwindowProc 함수 때문이였다.
윈도우 이동 , 크기변경 메세지가 들어왔는데 case문에는 설정 해 놓지 않았으니 Case문을 바로 빠져나가고 ,
DefwindowProc함수를 리턴시켜 해당 메세지의 기능을 실행 시키는 것이다.
그리고 어차피 DefWindowProc을 리턴 안시켜주면 에러가 난다. 꼭 포함시켜야 겠다.
마치며..
이런 하나하나의 설명을 못들어서 이해가 안가는 부분이 많았는데, 까보니 조금은 구조에 대해 이해가 된다.
일어나서 2,3일차 정리를 해야겠다..
'프로그래밍 공부 > C++ 프로그래밍' 카테고리의 다른 글
Direct X 5일차💻 <WINAPI> #3 (0) | 2022.06.19 |
---|---|
Direct X 4일차💻 <WINAPI> #2 (0) | 2022.06.19 |
Direct X 3일차💻 <WINAPI> #1 (0) | 2022.06.19 |
Direct X 2일차💻 C++의 동적 배열 (0) | 2022.06.15 |
Direct X 수업 2일차💻 스마트 포인터 (0) | 2022.06.15 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!