WIN32API FrameWork/원본

WIN32API 기본 코드 살펴보기

hyrule 2022. 5. 2. 17:06
// _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;
}