본문 바로가기
Win32 API/기초

Win32 API 기초 : Core 클래스 (2)

글: 시플마 2024. 6. 26.

CCore 클래스로 만든 싱글톤 객체를 초기화하기 위한

Init 함수를 통해 해상도를 조절할 것입니다.

 

인자로 값을 받았고 이 값을 통해 해상도를 나타내는 멤버를 초기화하였죠.

이제 해상도를 실제로 바꾸고자 합니다.

 

24번째 줄에서 보이는 함수를 통해 프로그램을 실행했을 때 

띄워지는 윈도우의 해상도를 설정할 수 있습니다.

 

 

그런데 SetWindowPos 함수를 통해 넣는 값은

작업 영역은 물론 타이틀 바와 메뉴 바, 그리고 

윈도우의 테두리 등 모두를 포함한 해상도죠.

 

하지만 우리가 원하는 것은 작업 영역에 대한 해상도입니다.

 

그래서 작업 영역을 우리가 원하는 해상도로 설정하려면

모든 요소의 해상도까지 더한 값을 넣어줘야

그나마 작업 영역의 해상도가 원하는대로 설정이 되겠죠.

 

코드 작성자가 직접 모든 요소를 고려하여 

해상도를 계산하고 SetWindowPos 함수에 값을

넣는 것은 어렵기 때문에 AdjustWindowRect 함수를 통해

모든 요소를 포함한 좌푯값을 얻을 수 있습니다.

 

AdjustWindowRect 함수의 첫 번째 인자는

사각형의 주솟값을 넘기면 됩니다.

 

여기서 사각형은 윈도우를 띄울 위치와

해상도 정보를 갖고 있는 사각형이죠.

 

LPRECT 자료형인데 확인해 보면

 

사격형 구조체를 가리키는 포인터임을 알 수 있습니다. 

 

 

두 번째 인자로는 윈도우의 스타일 정보를 넘깁니다. 

 

그 중 WS_OVERLAPPEDWINDOW는

기본적으로 많이 사용되는 스타일의 조합입니다.

 

윈도우의 스타일이라는 것은

 

최소화, 닫기 버튼의 존재, 스크롤의 존재 여부, 윈도우가 

보이게 할지 안 보이게 할지 등 윈도우 설정을 의미합니다.

 

2792번째 줄에 있는 WS_MINIMIZE를 활성화하면

 

위 사진처럼 최상위 비트 4개 중

2에 해당하는 곳이 1이 될 겁니다.

 

WS_OVERLAPPEDWINDOW는 최소화, 최대화, 닫기 버튼 등

기본적인 요소를 조합한 하나의 스타일입니다.

 

WS_OVERLAPPEDWINDOW의 경우

비트를 확인하면 위 그림과 같이 되겠죠?

 

이렇게 모든 옵션을 합쳐서 하나로 표현함으로써 

좋은 점은 AdjustWindowRect 함수에 옵션을

하나씩 넣을 필요가 없다는 것이죠.

 

만약 하나로 합치지 않고 진행한다면

 

AdjustWindowRect(&rec, true, false, false, true ... WS_OVERLAPPEDWINDOW, true);

 

위 코드처럼 모든 옵션에 대해, 사용할 옵션은 true로

사용하지 않은 옵션은 false로 하나씩 설정하여

인자로 넘겨야겠죠.

 

 

세 번째 인자로는 메뉴 바의 존재 여부인데

메뉴 바가 존재하므로 true로 넘겨주었습니다.

 

 

이제 실행해서 값을 확인해 보면

 

주솟값을 넘겼던 사각형의 값이 바뀌었습니다.

 

기존 사각형은 좌상단에서 좌측 좌푯값이 0이었는데

-8만큼, 즉 왼쪽으로 8 픽셀이 더 필요하다는 것이죠.

 

윈도우의 테두리가 8 픽셀을 차지하기 때문입니다.

 

 

위쪽 좌푯값은 무려 -51이 되었습니다.

원래 0이었으므로 위쪽으로 51 픽셀이 필요한 것이죠.

 

위쪽에는 메뉴 바, 타이틀 바, 윈도우 테두리가 

있으므로 더 많이 필요하네요.

 

 

오른쪽으로는 기존 1280에서 1288, 

마찬가지로 윈도우 테두리를 고려해야 하므로

8 픽셀이 더 필요하네요.

 

 

아래쪽으로도 마찬가지입니다.

기존 768에서 776, 8 픽셀이 더 필요합니다.

 

 

생각해 보면 AdjustWindowRect 함수의

 

첫 번째 인자는 값을 넣는 역할을 함과 동시에,

반환된 값을 담는 역할도 합니다.

 

그래서 첫 번째 인자인 lpRect의 주석을 보면 

_Inout_이라고 되어 있죠? 

 

AdjustWindowRect 함수는 lpRect를 통해

값을 받기도 하지만 함수가 값을 반환하면

lpRect를 통해 반환하기도 하는 것이죠.

 

 

이제 어떤 값을 SetWindowPos 함수에 넣어야

원하는 해상도를 얻을 수 있는지 알았으므로

적용해 봅시다.

 

SetWindowPos 함수의 첫 번째 인자로

메인 윈도우의 핸들을 넘깁니다.

 

WinApi의 함수들은 어떤 동작을 하려고 할 때

그 동작을 적용하는 윈도우가 어떤 것인지

항상 알려줘야 하는 느낌이죠.

 

C 스타일로 작성했기 때문입니다.

 

만약 C++ 스타일로 작성하였다면

함수를 호출할 때 윈도우 핸들을 넘기는 것이 아니라,

윈도우 핸들이 멤버 함수를 호출하는 식으로 작동했을 겁니다.

 

 

두 번째 인자는 필요 없으므로 nullptr로 주겠습니다.

 

 

세, 네 번째 인자는 윈도우가 띄워질 위치의 좌푯값(좌상단)입니다.

 

 

5, 6번째 인자는 각각 가로와 세로 길이를 넣어 주면 됩니다

여기서 아까 AdjustWindowRect 함수를 통해 얻은 값이 중요합니다.

 

원하는 해상도로 윈도우의 가로 길이를

설정하고 싶으면 AdjustWindowRect 함수를 통해 얻은

오른쪽 좌푯값에서 왼쪽 좌푯값을 빼면 되겠죠.

 

원하는 해상도로 윈도우의 세로 길이를

설정하고 싶으면 AdjustWindowRect 함수를 통해 얻은

아래 좌푯값에서 위쪽 좌푯값을 빼면 될 겁니다.

 

 

마지막 인자는 아무 의미 없으므로 0으로 하겠습니다. 

 

 

그리고 실행해 보니 

 

처음 기본 해상도로 프로그램을 실행했을 때와는

다른 크기의 윈도우를 띄우는 것이 확인됩니다.

 

 

 


 

 

 

사각형을 그려서 정말 1280 x 768의 해상도를

가진 윈도우가 맞는지 확인해 보겠습니다.

 

사각형을 그리기 위해 WndProc 함수의 

 

WM_PAINT case에 173번째 줄에 보이는

Rectangle 함수를 넣었습니다.

 

100 x 100 사이즈의 사각형을 그려 보죠.

왼쪽 좌푯값으로 1180, 위쪽에 딱 붙일 것이므로 

위쪽 좌푯값은 0, 오른쪽 좌푯값은 1280으로 줍니다.

윈도우의 가로 해상도가 1280이라면 사각형이

오른쪽에 딱 맞게 걸리겠죠. 그리고 사각형의 

세로 길이는 100이므로 아래 좌푯값은 100으로 합니다.

 

그리고 실행해 보니

 

우상단에 딱 붙은 사각형이 출력됨으로써

가로 해상도가 1280로 잘 설정된 것을 알 수 있습니다.

 

 

173번째 코드를 아래처럼 수정하여

 

실행해 보면 

 

이번에는 우하단에 딱 붙어서 사각형이 출력됩니다.

 

세로 해상도도 768로 잘 설정된 것을 의미하죠.

 

 

 


 

 

 

메시지 기반 방식을 통해 프로그램이 돌아가는 것이 아닌,

메시지가 발생하지 않아도 프로그램이 돌아가게 하기 위해

CCore 클래스로 만든 싱글톤 객체의 멤버 함수로 progress를 두었죠.

 

메시지가 발생하지 않는 모든 시간에 progress 함수가 호출되면서

각종 그리기, 키 입력과 같은 동작을 수행할 것입니다.

 

 

그리기 위해서 progress 함수에 

 

WM_PAINT case에서 사용된

BeginPaint 함수를 사용하지 않을 겁니다.

 

BeginPaint 함수는 EndPaint와 반드시 함께 사용되어야 합니다.

 

무효화 영역이 발생하여 WM_PAINT case 안의 코드가

실행되면 BeginPaint 함수를 통해 그리기를 시작할 수 있게 되죠?

 

이후 다 그리고 나면 EndPaint 함수를 호출하여

무효화 영역이 사라졌다고 윈도우에 알리며 그리기를 끝냅니다.

 

그리기를 끝낸 후에 EndPaint 함수를 호출하지 않으면

메시지 큐에 WM_PAINT가 계속 쌓이게 되어 문제가 발생합니다.

 

 

그래서 이런 메시지 기반의 그리기 방식을 사용하지 않기 위해

 

CCore 클래스에 멤버 함수 m_hdc를 추가했습니다.

 

그리고 Init 함수에 

 

m_hdc에 윈도우의 DC를 반환하는

GetDC 함수를 통해 DC를 대입합니다.

 

그리고 CCore 클래스 소멸자에 

 

ReleaseDC 함수를 추가합니다.

 

DC를 해제하는 함수로

첫 번째 인자는 윈도우 핸들을, 

두 번째 인자로 DC를 넘겨줍니다.

 

이런 식으로 소멸자에 DC 해제를 위한

함수를 넣어주면 프로그램이 종료될 때 알아서

DC가 해제되겠죠. CCore 클래스의 소멸자가

호출되었다는 것은 data 영역에 올라가 있는

싱글톤 객체가 사라진다는 것이고 이는 곧,

프로그램의 종료를 의미하기 때문이죠.

 

 

progress 함수에서 DC를 이용해 

 

사각형을 그릴 것입니다.

 

키 입력 등을 통해 사각형의 위치가 변경되면

update 함수를 통해 사각형의 위칫값을 업데이트해 줍니다.

 

그 다음 사각형의 위칫값과 사이즈값을 토대로

render 함수를 통해 실질적으로 사각형을 그리게 되죠.

 

위 두 함수는 CCore 클래스에서만 사용되기 때문에

 

private 멤버 함수로 선언하였습니다.

 

 

위치를 바꿀 수 있는 사각형을 그리기 위해서는 객체가 하나 필요하겠죠.

 

이를 위해서 Object라는 필터를 만들고

그 안에 CObject라는 클래스를 만들었습니다.

 

 

해당 클래스로 만든 객체는

 

좌푯값(중앙)과 크기 값에 대한 정보를 저장하고 있습니다.

 

 

이 사각형 객체에 대한 초기화는

 

CCore 클래스로 만든 싱글톤 객체의 멤버 함수 Init이 호출되면

그때 같이 진행해 줄 겁니다.

 

29번째 줄에서 가로 해상도의 절반에 해당하는 위치를 x 값으로,

세로 해상도의 절반에 해당하는 위치를 y 값으로 주었습니다.

 

작업 영역 중 가운데에 사각형이 출력되게 할 것이기 때문이죠.

 

 

30번째 줄에서 사각형의 가로와 세로의 길이를 100으로 설정하였습니다.

 

 

render 함수에서는

 

Rectangle 함수를 통해 그릴 사각형의

왼쪽 좌푯값은 사각형의 중앙 x 좌푯값에서 사각형 가로 길이의 절반만큼 뺀 값,

위쪽 좌푯값은 사각형의 중앙 y 좌푯값에서 사각형 세로 길이의 절반만큼 뺀 값,

오른쪽 좌푯값은 사각형의 중앙 x 좌푯값에서 사각형 가로 길이의 절반만큼 더한 값,

아쪽 좌푯값은 사각형의 중앙 y 좌푯값에서 사각형 세로 길이의 절반만큼 더한 값으로 합니다.

 

그 다음 실행해 보면

 

중앙에 사각형이 그려진 것을 확인할 수 있습니다.

 

 


 

 

사각형을 그렸으니

이번에는 키 입력을 하여 움직여 봅시다.

 

update 함수를 아래와 같이 작성합니다.

 

메시지 기반 방식의 경우 키 입력을 하면 키 입력을 인식하고 

어떤 키가 입력되었는지 판단하고 동작을 수행했다면,

이제는 update라는 함수가 호출되어 실행되는 그 시점에서

어떤 키가 입력되었는지 판단하는 방식으로 갈 겁니다.

 

비동기 키 입출력 함수인 GetAsyncKeyState 함수를 통해

키 입력을 받을 겁니다. 비동기이기 때문에 윈도우가 포커싱되어 

있지 않아도, 또는 윈도우가 내려가 있어도 키 입력을 인식하겠죠.

 

이는 문제가 생길 여지가 있으므로 추후에 수정을 해야 합니다.

 

GetAsyncKeyState 함수는 인자로 키를 넘깁니다.

만약 인자로 받은 키가 입력되었다면 if 문 안의 코드가 실행되겠죠.

 

근데 GetAsyncKeyState 함수는 키 입력 여부에 따라

true, false를 반환하는 게 아니라 상태를 반환합니다. 

 

예를 들어 키가 눌리긴 눌렸는데, 이전에 눌렸는데

현재는 떼진 상태인지, 또는 현재는 눌린 상태인데

이전에는 눌린 적이 없는지 등 다양한 상태를 반환하죠.

 

근데 우리가 필요한 건 단순히 해당 키가 눌렸는지만

알면 되기 때문에 반환값에 0x8000을 & 연산하게 되면

키가 눌렸다는 것에 대한 상태값만 얻을 수 있죠.

 

그래서 왼쪽 키가 눌리면 사각형의 현재 x 좌푯값에서 1을 빼주고

오른쪽 키가 눌리면 사각형의 현재 x 좌푯값에서 1을 더합니다.

 

그리고 실행하여 왼쪽, 오른쪽 키 입력을 진행하면

 

한 번 눌렀음에도 상당히 많이 움직이고

사각형이 이동하면서 검은색 잔상이 남습니다.

 

이유가 무엇일까요?

 

사각형이 많이 움직이는 이유는

처리 속도가 너무 빨라서입니다.

 

사용자가 키를 입력하는 짧은 시간에

progress 함수를 얼마나 실행하는지 확인해 봅시다.

 

 

progress 함수가 실행될 때마다 iCount의 값을 하나 올립니다.

 

progress 함수가 몇 번 실행되었는지 나중에 

iCount의 값을 통해 알 수 있을 겁니다.

 

static으로 선언하여 progress가 실행될 때마다 

0으로 초기화가 일어나지 않도록 방지합니다.

 

 

변수 prevTime에 현재 시점을 저장합니다.

다음 curTime에 현재 시점을 저장합니다.

 

prevTime은 정적 변수이기 때문에 값이 최초

한번만 초기화되고 curTime은 시간이 지남에 따라

계속 값이 올라가죠. curTime에서 prevTime을 뺀 값이 

1000을 넘으면, 즉 과거 시점으로부터 현재 시점이 

1초가 지난 시점이라면 46번째 줄에 있는 if 문 안의 

코드가 실행됩니다. 과거 시점을 현재 시점으로 갱신하고

iCount 값을 0으로 바꿉니다.

 

 

48번째 줄에 중단점을 걸고 디버깅을 해 보면

iCount의 값이 34460인 것을 확인할 수 있습니다.

 

이것은 1초 동안 progress 함수가

34460번 실행되었음을 의미합니다.

 

그래서 살짝 눌렀음에도 progress 함수가

상당히 많이 반복되어 사각형이 쭉 움직이는 것입니다.

 

현재 윈도우의 가로 해상도가 1280 픽셀인데 

34460 픽셀만큼 이동하면 이미 윈도우의

범위를 넘어가게 되죠.

 

 

또한 키 입력을 통해 사각형이 왼쪽이나 오른쪽으로

이동하긴 하지만 이전 위치에 있던 사각형의 테두리가

그대로 남아 검은색으로 칠해지는 것처럼 보이는 것이죠.

 

 

 


강의 출처 : https://www.youtube.com/watch?v=P_3_gcLQm1k&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=9


'Win32 API > 기초' 카테고리의 다른 글

Win32 API 기초 : Timer (2)  (0) 2024.06.29
Win32 API 기초 : Timer (1)  (0) 2024.06.27
Win32 API 기초 : Singleton (2)  (0) 2024.06.24
Win32 API 기초 : Core 클래스 (1) / Singleton (1)  (0) 2024.06.23
Win32 API 기초 : PeekMessage (2)  (0) 2024.06.22