4 minutes
🪟 Windows API 기초
1. Visual Studio 프로젝트 설정
Visual Studio 프로젝트 설정의 Linker > System에서 SubSystem을
Windows (/SUBSYSTEM:WINDOWS)로 변경기존 설정(
Console (/SUBSYSTEM:CONSOLE))은 진입 함수가main()으로, 일반적인 CLI 프로그램이지만 이를Windows로 바꾸면 진입 함수가WinMain()로 바뀌면서 GUI 애플리케이션 진입 모델로 바뀜이는 프로젝트를 콘솔 창 없는 순수 GUI 앱 개발 형태로 바꾸어주며, 메시지 루프(Message Loop)가 메인 프로그램 구조가 됨
2. 진입 함수
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
)
{
return 0;
}
WINAPI: Windows API가 기대하는 함수 호출 규약HINSTANCE hInstance: 현재 실행 중인 프로그램(또는 모듈)의 핸들HINSTANCE hPrevInstance: 이전 인스턴스의 핸들- 과거 16bit Windows 시절 유물이라 현대 Windows에서는 항상
nullptr
- 과거 16bit Windows 시절 유물이라 현대 Windows에서는 항상
LPSTR lpCmdLine: 명령행 인자의 문자열int main(int argc, char** argv)의argv와 대응되는 개념으로, 프로그램 이름(.exe파일 경로)은 제외된 문자열파싱은 직접 해야함
int nShowCmd: 윈도우를 처음 띄울 때의 표시 상태SW_SHOW: 일반 표시SW_SHOWMAXIMIZED: 최대화SW_SHOWMINIMIZED: 최소화SW_HIDE: 숨김사용자가 바로 전에 창을 특정 상태로 종료한다면 다음 실행 시 그 상태를 OS가 기억하여 전달함
3. Window Procedure(메시지 처리기)
Win32 GUI 프로그램에서 가장 핵심적인 콜백 함수
OS가 이 창에 무슨 일이 생겼다고 알려줄 때마다 호출되는, 이벤트가 들어오는 공식 경로
키보드, 마우스, 창 크기 변경, 종료 요청 등 모든 윈도우 이벤트가 메시지로 전달되며, Win32 프로그램에서 이 함수가 없으면 창은 사실상 아무 것도 못 함
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{
...;
}
LRESULT: 메시지 처리 결과를 OS에 돌려주는 반환 값프로그램에서 처리하면 보통
0기본 처리 맡기면
DefWindowProc()(Windows 기본 처리 루틴)의 반환값
CALLBACK: Windows API가 기대하는 호출 규약HWND hWnd: 메시지가 발생한 윈도우의 핸들- 멀티 윈도우 앱이면 어떤 창인지 구분하는 용도
UINT message: 무슨 일이 일어났는지 나타내는 메시지 IDWM_PAINT: 다시 그려라WM_KEYDOWN: 키가 눌렸다WM_SIZE: 창 크기 변경됨WM_DESTROY: 창이 파괴됨
WPARAM wParam: 메시지에 따른 부가 정보(작은 의미 값)WM_KEYDOWN의 경우 눌린 키의 코드WM_SIZE의 경우 최소화 / 최대화 여부
LPARAM lParam: 메시지에 따른 부가 정보(구체적인 데이터)WM_MOUSEMOVE: 마우스 좌표WM_SIZE: 새 가로 / 세로 크기
메시지 처리 분기 내
default: return DefWindowProc(hWnd, message, wParam, lParam);- 우리가 처리하지 않은 메시지는 Windows 기본 처리 루틴에게 위임
4. Window Class(윈도우 클래스)
Win32에서 창을 하나 만들 때 OS는 각종 정보를 필요로 함
이 창의 메시지를 누가 처리할 것인가(
WndProc())기본 배경색
기본 커서
아이콘
창의 종류
이런 정보들을 윈도우 클래스라는 단위로 한 번 정의
WNDCLASSW wndclass = { 0, // style WndProc, // lpfnWndProc 0, // cbClsExtra 0, // cbWndExtra 0, // hInstance 0, // hIcon 0, // hCursor 0, // hbrBackground 0, // lpszMenuName WindowClass // lpszClassName };이런 형태의 창이 있음을 등록:
RegisterClassW(&wndclass)이후 윈도우 클래스를 이용하여 창 생성
HWND CreateWindowExW( // 창 생성에 성공하면 유효한 윈도우 핸들 반환 DWORD dwExStyle, // 확장(Ex 함수) 윈도우 스타일 LPCWSTR lpClassName, // 사용할 윈도우 클래스 이름 LPCWSTR lpWindowName, // 타이틀바에 표시될 문자열 DWORD dwStyle, // 창의 모양과 동작을 정의하는 플래그 int X, // 창 가로 위치 int Y, // 창 세로 위치 int nWidth, // 초기 클라이언트 영역 너비 int nHeight, // 초기 클라이언트 영역 높이 HWND hWndParent, // 부모 윈도우 HMENU hMenu, // 메뉴 핸들 HINSTANCE hInstance, // 프로그램 인스턴스 핸들(WinMain에서 받은 것) LPVOID lpParam // 추가 사용자 데이터 );
유니코드 문자열
코드 내에 사용되는
WCHAR나 함수 이름 뒤의W는 유니코드(Wide, 16bit)를 사용함을 의미하며, 여기에 문자열을 사용할 때는 일반적인 문자열을 사용하면 안되고, 유니코드 문자열 리터럴(L"...")을 사용해야 함반면 ANSI 버전(
A가 붙음)은 그냥 문자열로 써도 되지만, 왠만하면W형태로 통일하는 것이 좋음
5. Message Loop(메시지 루프)
- 보통 메인 루프 내에서 동작
while (bIsExit == false) // 메인 루프
{
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) // 메시지 루프
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT)
{
bIsExit = true;
break;
}
}
// 매번 실행되는 로직
}
// 소멸 로직
PeekMessage(): 윈도우 메시지가 있으면 가져오고(MSG msg에 저장), 없으면 즉시 false 반환게임 / 그래픽 앱에서 보통 사용하며, 전통적인 GUI 앱에서 사용하는
GetMessage()는 메시지가 올 때까지 프로세스를 블록함마지막 인자의
PM_REMOVE는 메시지를 읽은 후 큐에서 제거한다는 의미이며, 없으면 루프에서 같은 메시지를 계속 읽는 무한 루프에 빠짐
TranslateMessage(): 키보드 메시지를 문자 메시지로 변환키보드 입력 메시지를 가공하여
WM_CHAR생성텍스트 입력이 필요한 경우에는 필수이며, 마우스 메시지에는 영향 없음
DispatchMessage(): 해당 윈도우의WndProc(hWnd, msg.message, msg.wParam, msg.lParam)을 호출
WM_QUIT
PostQuitMessage()로 생성되는 윈도우 메시지WndProc로 전달되지 않으며, 메시지 큐에서만 처리되기 때문에 이 윈도우 메시지를 통해 메인 루프를 해제할 수 있음