// _220428_Win32API.cpp : 애플리케이션에 대한 진입점을 정의합니다.
/*
[Win32API]
<Win32API 개요>
* 어렵게 생각할 필요 없이, Windows에서 제공하는 함수의 집합이다.
- 예를 들어 우리가 게임을 돌리고 있을 때 절전모드에 진입하는걸 막는다던지 하는 것들.
* 프로젝트를 만들 때 'Windows 데스크톱 마법사'로 만들면 된다.
- 데스크톱 애플리케이션(.exe) 선택
cf) 동적 연결 라이브러리(.dll), 정적 연결 라이브러리(.lib)
- 일종의 라이브러리라는 것을 만들어서
우리가 만든 소스코드를 다른 프로젝트에서도 갖다 쓸 수 있게 한 것.
- 작성된 코드는, 사전 설정에서 '빈 프로젝트' 체크박스를 해제하여 나오는
빈 Windows라는 창을 띄워주는 기본 코드에 주석을 단 것이다.
- 다음 수업에서는 이 코드들을 기반으로 2D게임의 프레임워크를 짜 나갈 예정이다.
* 앞으로는 파일의 크기가 커질 예정이라 매일 코드를 압축해서 백업하기 힘들다.
- 그러므로 앞으로는 SVN(SubVersion)이라는 시스템을 사용하여 코드를 백업할 예정이다.
*/
/*
* Window: 메시지 기반의 운영체제이다.
* 우리가 마우스 조작, 키보드 조작 등을 하는 모든 행위를 '이벤트'라고 한다.
* 이러한 이벤트가 발생 되면 윈도우즈라는 운영체제는 모든 행위의 이벤트들을 '메시지'로 바꾼다.
* 마우스를 움직이거나 클릭, 키보드를 입력 등 모든 행위가 이벤트가 되고
해당 이벤트를 메시지로 변경하여 들어오는 메시지에 따라 원하는 동작을 만들어주는 개념이다.
- 메시지가 들어오지 않으면 메시지가 들어올 때까지 계속 대기를 하고 있음.
* 모든 프로그램에는 메시지를 저장하는 메시지 큐가 존재하고 선입선출로 처리한다.
* 우리는 메시지 큐를 while문으로 계속 받아서 처리해줘야 하는 것이다.
* 1바이트 문자열을 사용 = 멀티바이트
* 2바이트 문자열을 사용 = 유니코드(윈도우즈 기본)
- 프로젝트 우클릭 - 속성 - 고급 - 문자 집합에서 확인 가능.
- 우리는 앞으로 유니코드 문자 집합을 사용한다.
*/
#include "framework.h"
#include "_220428_Win32API.h"
#define MAX_LOADSTRING 100
// 전역 변수:
HINSTANCE hInst; // 현재 인스턴스입니다.
//윈32 API를 보면 변수명 앞에 H가 붙어있는 경우를 많이 볼텐데,
//이것은 '핸들'이다. -> 운영체제에서 만들어서 제공해준다.
//우리는 이 핸들을 이용하여 운영체제의 기능들을 사용할 수 있게 요청을 할 수 있다.
//윈도우에는 굉장히 많은 프로그램이 돌아가고 있다.
//그 프로그램들을 구분하기 위해서 핸들을 만드는 것이다.
//같은 프로그램을 여러개의 창을 띄워도 HINSTANCE는 하나만 할당된다.
//프로그램 식별 번호 라고 생각하면 될 듯.
/* 마찬가지로 필요 없음.
WCHAR szTitle[MAX_LOADSTRING]; // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING]; // 기본 창 클래스 이름입니다.
*/
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
//콘솔에서의 main()과 같은 함수.
//방금 위에서 본 HINSTANCE를 인자로 받고 있다.
//나머지 3개 항목은 필요 없음.
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
/*
//안 씀.
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 여기에 코드를 입력합니다.
// 전역 문자열을 초기화합니다.
//↓ 두 개 쓸모 없음. win32 시절의 잔재인데 없애기 힘들어서 냅둔 거임.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_MY220428WIN32API, szWindowClass, MAX_LOADSTRING);
*/
//↓ 중요.
MyRegisterClass(hInstance);
//우리가 프로그램을 실행하면 윈도우즈는 기본적으로 레지스트리를 가지고 있다.
//이 함수는 레지스트리에 등록할 윈도우 구조체를 만들서 해당 구조체를 반환해 준다.
// 애플리케이션 초기화를 수행합니다:
//-> InitInstance 함수를 통해서
//윈도우 창을 생성(실패 시 FALSE 반환)
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
//아직 필요 없음
//HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY220428WIN32API));
//운영체제가 만들어준 메시지를 얻어오기 위한 구조체이다.
MSG msg;
// 기본 메시지 루프입니다:
/*
GetMessage: 메시지 큐에서 메시지를 꺼내오는 함수이다.
단, 메시지 큐가 비어있을 경우 메시지가 들어올 때까지 빠져나올 수 없다.
이렇게 멈춰서 대기하고 있는 것을 블로킹 모드라고 한다.
큐가 비어서 멈춰있는 시간을 윈도우 데드타임이라고 한다.
게임의 경우에는 잘 쓰이지 않는다.
사람이 아무리 키보드 마우스로 입력을 많이 해 봐야 컴퓨터가 처리하는 데에는
찰나의 시간밖에 안 걸리기 때문에, 데드타임이 매우 길기 때문이다.
그렇기에 게임의 경우에는 PeekMessage라는 함수를 사용한다.
메시지 큐에서 메시지를 꺼낸 뒤의 데드타임동안 프레임을 그리는 것이다.
&msg: 콜 바이 어드레스
*/
while (GetMessage(&msg, nullptr, 0, 0))
{
//닫기 명령이 들어가면 GetMessage 함수가 false를 반환하여
//반복문이 종료되고 프로그램이 종료된다.
/*
TranslateMessage - DispatchMessage: 일종의 한 세트
메시지큐에서 꺼내온 메시지를 TranslateMessage 함수로 넘겨주면
문자 키인지 F1, 방향키 같은 키인지를 판단해준다.
이러한 키들은 WM_KEYDOWN으로 메시지가 인식이 되고
문자 키는 WM_CHAR로 인식이 된다.
키를 누르면 문자키의 경우 WM_CHAR도 만들어져야 하기 떄문에
여기에서 WM_KEYDOWN이 일어나면 문자키의 경우 WM_CHAR메시지를 추가로 만들어서
메시지 큐에 넣어준다.
이 함수가 없을 경우 문자 키를 눌렀을 때 WM_CHAR가 발생하지 않으므로
반드시 써 줘야 한다.
*/
TranslateMessage(&msg);
//DispatchMessage 함수는 메시지큐에서 꺼내온 메시지를 메시지 처리 함수에 보내준다.
//WndProc로 보내주는 것이다.
DispatchMessage(&msg);
//밑의 if문은 아직 쓰지 않음. 안의 알멩이만 꺼내온다.
/*
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
*/
}
return (int) msg.wParam;
}
//
// 함수: MyRegisterClass()
//
// 용도: 창 클래스를 등록합니다.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
//레지스터에 등록할 윈도우 클래스 구조체를 만들어준다.
WNDCLASSEXW wcex;
//wcex의 사이즈를 저장. 존재이유는 잘 모름.
wcex.cbSize = sizeof(WNDCLASSEX);
//▼ 아래에 윈도우가 어떤 형태의 윈도우를 생성할지를 설정할 수 있다.
wcex.style = CS_HREDRAW | CS_VREDRAW;
//CS_HREDRAW: Horizontal Redraw | CS_VREDRAW Vertical Redraw
//가로세로 영역 크기가 바뀌었을 때 클라이언트 영역을 전부 다시 그려주는 기능
//WndProc(Window Procedure) 전역함수 -> 함수 포인터
//이벤트 -> 메시지 -> 메시지 큐 -> 메시지를 인자로 집어넣어서 처리해줄 함수를 지정
//메시지 큐에서 꺼내온 메시지를 인자로 전달하며 호출할 함수의 함수 주소를 등록한다.
//이런 식으로 함수 포인터를 등록해놓고 호출하는 방식을 '콜백'이라고 한다.
wcex.lpfnWndProc = WndProc;
//예약용(안쓰임)
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
//윈도우 인스턴스를 등록한다.
wcex.hInstance = hInstance;
//실행파일에 뜨는 아이콘
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY220428WIN32API));
//커서 변경. ex)IDC_WAIT: 대기
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
//배경화면
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
//왼쪽 위에 뜨는 메뉴를 지정. nullptr을 등록하면 메뉴가 뜨지 않음.
//게임을 만들 떄는 메뉴를 쓰지 않으므로 nullptr을 많이 씀
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MY220428WIN32API);
/*
* 기본 설정은 프로젝트 '리소스 파일' 안의 StringTable에 들어있는 데이터를
불러와서 이름을 지정해주지만, 굳이 그렇게 할 필요 없이 여바로 지정해주어도 된다.
* 등록할 클래스의 이름을 유니코드 문자열로 만들어서 지정한다.
유니코드 형태의 문자열로 설정하려면 L"(문자열)" 형식으로 하면 된다.
* 하지만 TEXT("문자열")로 하는 것이 더 좋다.
TEXT 매크로는 프로젝트 설정이 유니코드로 되어있을 경우 유니코드 문자열로 만들어지고
멀티바이트로 되어있을 경우 멀티바이트 문자열로 만들어지게 된다.
*/
//wcex.lpszClassName = szWindowClass;
wcex.lpszClassName = TEXT("Tutorial");
//윈도우 창 왼쪽 위에 뜨는 작은 아이콘
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
//구조체를 만들어서 등록해주는 함수.
return RegisterClassExW(&wcex);
}
//
// 함수: InitInstance(HINSTANCE, int)
//
// 용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
// 주석:
//
// 이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
// 주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
/*
* 1번 인자는 윈도우 클래스에 등록할 이름이다.
* 2번 인자는 타이틀바에 표시할 이름이다.
- 1, 2번 인자의 기본값인 szWIndowClass와 szTitle 대신
그냥 문자열을 넣어 생성해줄수도 있다.
* 3번 인자는 이 윈도우 창이 어떻게 생성될지를 결정하는 선택 인자이다.
- 현재 인자를 WS_OVERLAPPEDWINDOW의 정의로 타고 들어가보면 여러가지 옵션이
조합되어 있는 것을 알 수 있다.
* 4, 5번 인자는 윈도우 창이 생설될 화면에서의 위치를 지정한다.
픽셀로 지정한다.
- 예를 들어 1920, 1080 해상도라면 거기에서 원하는 값을 넣어주면
해당 위치에 나오게 된다.
- 4번은 가로좌표, 5번은 세로좌표.
* 6번, 7번 인자는 윈도우창의 가로, 세로의 크기를 지정한다.
픽셀단위로 지정을 해준다.
* 8번 인자는 부모 윈도우가 있다면 부모 윈도우의 핸들을 지정한다.
없으면 nullptr을 지정한다.
* 9번 인자는 메뉴가 있다면 메뉴 핸들을 넣어주고 없으면 nullptr을 지정한다.
* 10번 인자는 윈도우 인스턴스를 지정하여 이 윈도우 인스턴스에 속한
윈도우 창을 만들어지게 된다.
* 11번 인자는 필요 없음.
* 이런 식으로 윈도우 창을 만들어주고 정상적으로 만들어졌다면
생성된 윈도우 창의 핸들을 반환해준다.
* HWND = Handle WiNdoW 윈도우 핸들
*/
HWND hWnd = CreateWindowW(TEXT("Tutorial"), TEXT("TutorialWindow"), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
/*
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED: 일반적인 윈도우 | \
WS_CAPTION: 창 이름 표시 | \
WS_SYSMENU: 시스템 메뉴(창 닫기 등) | \
WS_THICKFRAME: 테두리 | \
WS_MINIMIZEBOX: 최소화 가능 | \
WS_MAXIMIZEBOX: 최대화 가능)
#define WS_POPUPWINDOW (WS_POPUP: MessageBox와 같은 팝업 창 | \
WS_BORDER | \
WS_SYSMENU)
*/
if (!hWnd)
{
return FALSE;
}
//윈도우 창을 보여준다.
//1번인자에 들어간 핸들의 윈도우 창을 보여줄지 말지를 결정해준다.
//
//2번인자: 기본값인 nCmdShow 대신
//SW_SHOW(창을 보여준다), SW_HIDE(창울 숨긴다 - 백그라운드)를 넣어줄 수도 있다.
//cf)SW_HIDE 외에도 왼도우 서비스를 통해 창을 표시하지 않을 수도 있다.
ShowWindow(hWnd, SW_SHOW);
//이 함수를 호출하여 클라이언트 영역이 제대로 갱신되었다면
//0이 아닌 값을 반환하고
//갱신이 실패했을 경우 0을 반환한다.
UpdateWindow(hWnd);
return TRUE;
}
//
// 함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 용도: 주 창의 메시지를 처리합니다.
//
// WM_COMMAND - 애플리케이션 메뉴를 처리합니다.
// WM_PAINT - 주 창을 그립니다.
// WM_DESTROY - 종료 메시지를 게시하고 반환합니다.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//message 큐로부터 받은 message에 따라 어떤 동작을 할지 결정.
switch (message)
{
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;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}