1. VisualStudio에서 윈도우 창 생성
Windows 데스크톱 마법사 : 빈 상태의 window 앱을 생성합니다.
Windows 데스크톱 애플리케이션 : 기본 윈도우즈 생성창이 만들어져 있습니다.
오늘은 실행 흐름을 알아보기 위해 'Windows 데스크톱 애플리케이션'을 사용하여 생성합니다.
프로젝트를 생성 후, 바로 후 실행(ctrl+f5) 하면
위와같이 빈 윈도우즈창이 실행됩니다.
2. Window.cpp 파일 분석
1) windows 기반으로 만들 경우, 가장 큰 차이점은 "문자열" 입니다.
- 유니코드 베이스이기 때문에 문자 하나당 2byte로 만듭니다.
- 제목 표시줄 텍스트인 szTitle의 자료형인 WCHAR는 16비트 유니코드 캐릭터형인것을 확인하실 수 있습니다.
- 유니코드 대신 멀티 바이트 문자형 (c++ 기본 문자 베이스)로 바꾸고 싶을 경우
속성 - 고급 - 문자집합 - 유니코드로 되어있는 설정을 멀티 바이트 문자집합 사용으로 바꾸면 됩니다.
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
}
2) wWinMain()
- c++의 main()처럼 시작점(진입점, 엔트리포인트)가 되는 함수.
- 인자 앞에 H가 붙어 있는 게 보이는데, H가 앞에 붙어 있을경우 핸들의 약자입니다.
- HWND : 윈도우 핸들. 윈도우 창 하나 열릴 때 마다 하나씩 부여합니다.
- 윈도우 창이 열리면, 해당 창에 운영체제가 고유한 번호를 부여합니다.
- HINSTANCE : 모든 실행은 운영체제에게 허락을 받아야 합니다.
- HINSTANCE가 허락 받은 고유의 번호 역할을 합니다.
- ex) visual studio를 2개 실행할 경우 고유한 HINSTANCE가 각각 1개씩, 총 2개 부여됩니다.
- 프로그램마다 전혀 다른 HINSTANCE 하나가 부여됩니다.
- 하나의 HINSTANCE에서 여러개의 HWND가 존재할 수 있습니다.
- 고유한 번호는 임의로 발급할 수 없고, 운영체제만 발급할 수 있습니다.
- HINSTANCE가 허락 받은 고유의 번호 역할을 합니다.
// wWinMain() 내부
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 여기에 코드를 입력합니다.
// 전역 문자열을 초기화합니다.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_MY250212WINDOW, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 애플리케이션 초기화를 수행합니다:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY250212WINDOW));
MSG msg;
// 기본 메시지 루프입니다:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
- LoadStringW() : 스트링 테이블에서 문자열을 가져와서 사용합니다.
- ID에 해당하는 문자열을 가져와서 사용합니다.
- MAX_LOADSTRING : 읽어올 수 있는 최대치만큼 가져옵니다.
- 게임 만들땐 사용하지 않습니다.
// 함수: MyRegisterClass()
//
// 용도: 창 클래스를 등록합니다.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY250212WINDOW));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MY250212WINDOW);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
▶️ MyRegisterClass() : 운영체제에 등록할 윈도우 클래스를 만들고 등록합니다.
- WNDCLASSEXW : 운영체제에 등록할 윈도우 클래스 구조체
- cbSize : 구조체의 크기. 반드시 지정되어야 합니다.
- style : | (or) 을 붙여서 다시 그려줄건지 옵션을 지정합니다.
- HREDRAW : h는 horizontal 가로를 다시 그립니다
- VREDRAW : v는 vertical 세로를 다시 그립니다.
- 아래 그림에서 메뉴바, 타이틀바 등등을 제외하고 내용을 바꿀 수 있는 곳, 즉 화면에 출력 가능한 영역을 '클라이언트 영역' 이라고 합니다. 현재 옵션대로라면 클라이언트 영역의 크기(가로, 세로)가 변경될 경우 다시 그려주도록 하는 옵션입니다.
- lpfnWndProc :WndPrc() 함수입니다.
- 윈도우는 메세지 기반의 운영체제입니다.
운영체제는 발생하는 이벤트( 키보드 입력, 마우스 클릭, 마우스 움직임, 휠 굴리기 등) 를
메세지로 변경해서 이벤트가 발생한 윈도우에 메세지를 전송해줍니다. - 윈도우는 내부에 메세지 큐를 가지고 있습니다. 메세지큐에 운영체제로부터 전송받은 메세지가
저장되고 이 메세지를 가져와서 메세지에 맞는 작업을 수행해주는 방식으로 동작합니다. - lpfnWndProc = WndProc;는 메세지큐에서 얻어온 메세지를 인자로 넣어서 호출해줄 함수의 주소를 넘겨줍니다.
- 메세지가 발생할 경우 운영체제가 WndProc를 호출하게 됩니다.
- 윈도우는 메세지 기반의 운영체제입니다.
- cbClsExtra, cbWndExtra : 예약된 메세지
- hInstance : 운영체제에서 부여해준 HINSTANCE를 전달해줍니다.
- hIcon : 실행파일 아이콘을 지정합니다.
- hIconSm : 실행시 윈도우창 왼쪽 상단의 작은 아이콘을 지정해줍니다.
- 리소스뷰에서 이미지를 넣고 변경해도 되고, 도트 찍어도 됩니다
- hCursor : 윈도우 창에서의 커서 모양을 나타냅니다.
- hbrBackground : 크라이언트 영역의 색상을 지정합니다.
- lpszMenuName : 윈도우 메뉴를 지정합니다.
- 메뉴를 지우고 싶다면 0을 넣어주면 됩니다.
- spszClassName : 등록할 윈도우 클래스의 이름을 지정합니다.
- 등록에는 RegisterClassExW()함수를 이용합니다. 위에서 설정한 윈도우 클래스를 등록해줍니다.
//
// 함수: InitInstance(HINSTANCE, int)
//
// 용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
// 주석:
//
// 이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
// 주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
▶️ InitInstance() : 윈도우 창을 생성합니다.
- CreateWindowW() : 윈도우창을 생성해주는 함수입니다.
- WinAPI에서 함수명 뒤에 W가 붙으면 유니코드, A가 붙으면 멀티바이트 입니다.
- API : application program interface 앱을 개발하기 위한 기능들을 제공하는 집합
- win api는 윈도우에서 앱을 개발하기위한 기능들을 제공하는 집합. 함수형
- 1번인자 : 윈도우 클래스 이름을 지정합니다.
- 2번인자 : 윈도우 타이틀바에 출력할 이름을 지정합니다.
- 3번인자 : 윈도우 창의 모양을 결정합니다. (최소창, 창닫기 버튼을 뺀다던지..등등)
- 4번인자 : 화면에서 윈도우가 시작할 x지점을 지정합니다.
- 시스템좌표 : 모니터 화면 최상단 왼쪽이 0,0 기준. 오른쪽이 x+, 아래쪽이 y+
- 클라이언트 좌표계 : 클라이언트 영역 왼쪽 위 0,0 기준 오른쪽이 x+, 아래쪽 y+
- CW_USEDEFAULT : 윈도우가 알아서 위치를 잡아줌
- 5번인자 : 화면에서 윈도우가 시작할 y지점을 지정합니다.
- 6번인자 : 윈도우 창의 가로 크기를 지정합니다.
- 7번인자 : 윈도우 창의 세로 크기를 지정합니다.
- 8번인자 : 부모 윈도우가 있을 경우 부모 윈도우의 핸들을 지정합니다.
- 9번인자 : 메뉴 핸들을 전달합니다.
- 10번 인자 : 윈도우 인스턴스를 전달합니다. winMain 에서 전달은 값으로 전달해야 한다.
- 11번 인자 : 창 생성 데이터를 저장합니다. WM_CREATE는 윈도우 생성시 발생하는 메세지인데 이 메세지가 발생하면 WndProc함수의 IParam에 이 값이 전달됩니다. (잘 안씁니다)
- 이렇게 윈도우를 생성하면 윈도우 핸들을 만들어 줍니다.
- 잘못된 생성일 경우 0을 반환합니다.
- ShowWindow() : 위에서 만든 윈도우 창을 보여줄지, 숨길지를 결정합니다.
- UpdateWindow() 츨라이언트 영역을 강제로 다시 그리게 요청해주는 함수입니다. 처음 생성시나 특정 상황에 창을 새로고침 해야할 경우 사용합니다.
- WinAPI에서 함수명 뒤에 W가 붙으면 유니코드, A가 붙으면 멀티바이트 입니다.
다시 wWinMain()으로 돌아와서
// 운영체제가 만들어주는 메세지를 전달받기 위한 구조체
MSG msg;
// 기본 메시지 루프입니다:
// GetMessage() : 메세지큐의 메세지를 얻어오는 함수입니다.
// - 메세지 큐가 비어있다면 메세지가 들어올때 까지 대기하며 메세지가 들어오면 가져옵니다.
// - 1번인자로 메세지를 전달 받습니다.
// - 메세지가 없다면 무한정 대기합니다. 그래서 이걸로 게임만들긴 힘듭니다.
// - 메세지가 없는 시간을 '윈도우 데드타임'이라고 합니다.
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
// 키보드 입력 메세지가 발생할 경우 동작합니다.
// WM_KEYDOWN, WM_KEYUP 등 메세지가 발생하면 문자일 경우 WM_CHAR메세지를 하나더 만들어주는 역할을 합니다.
TranslateMessage(&msg);
// 메세지를 WndProc로 전달해 줍니다.
DispatchMessage(&msg);
}
}
// WinProc()를 호출합니다.
return (int) msg.wParam;
- 프로그램이 종료 (x버튼 누를때 까지) 까지 while문이 반복됩니다.
▶️ WinProc()
//
// 함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 용도: 주 창의 메시지를 처리합니다.
//
// WM_COMMAND - 애플리케이션 메뉴를 처리합니다.
// WM_PAINT - 주 창을 그립니다.
// WM_DESTROY - 종료 메시지를 게시하고 반환합니다.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// 사용 안하는 CASE문은 삭제하고 사용해도 됩니다.
switch (message)
{
// WM_COMMAND : 일반적인 메세지를 통칭.
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
// WM_PAINT : 윈도우 창에 그려야 하는 경우 동작합니다.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다.
EndPaint(hWnd, &ps);
}
break;
// WM_DESTROY : 윈도우 창 종료 메세지
case WM_DESTROY:
PostQuitMessage(0);
break;
// 위에서 지정한 메세지 외의 다른 메세지가 발생할 경우 윈도우의 기본 동작으로 처리가 되게 만들어줍니다.
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
⭐ 메세지 기반의 운영체제 동작방식은 잘 이해하고 있어야합니다.
기본코드를 건들일은 많이 있지 않지만, 일반적인 방법으로 처리되지않고 직접 만져야 하는 경우가 간혹있기 때문입니다.
'개발개발 > WinAPI & DirectX' 카테고리의 다른 글
연습 - 이동하는 적과 적의 총알 만들기 (0) | 2025.02.12 |
---|---|
키 입력, 총알 생성 및 이동처리 (0) | 2025.02.11 |
게임실행 단계 분리, 타이머 클래스, 클라이언트 영역 조절 (0) | 2025.02.10 |
기본 구조 설계 : 게임관리자/게임정보/메인 클래스 제작, 폴더 정리를 위한 솔루션 제거와 기존 프로젝트를 솔루션에 추가 (0) | 2025.02.03 |
화면에 도형, 문자, 선 출력하기 (0) | 2025.02.02 |
댓글