WIN32 Singleton

Date:     Updated:

카테고리:

태그:

4일차

오늘부턴 WINAPI의 구조를 바꿀 것이다.

  • Default
    • Client.h
    • framework.h
    • Resouce.h
    • targetver.h
  • Engine
    1. Header
      1. define.h
    2. Core
      1. CCore.cpp
      2. CCore.h
    3. Manager
      1. KeyMgr
        1. CkeyMgr.cpp
        2. CkeyMgr.h
  • 리소스 파일
  • main.cpp
  • pch.h.

형태로 구성된다.

  1. pch.h는 프로젝트의 미리 컴파일된 파일로 설정해놓는다.
#pragma once

//미리 컴파일된 헤더. 
//이미 제공된 애들 <- 바뀔 여지가 없으니까, 다시 컴파일 할 이유X
//따라서 이걸 사용하면, 컴파일 속도가 빨라짐. 

//그리고 클래스를 만들때 선언해둔 미리 컴파일된 헤더 생김. 

#include <Windows.h>
#include "define.h"
  1. define.h
#pragma once

//매크로는 코드를 치환하는 것. 
//따라서 함수를 호출한 적이 없어서 스위칭이 없어서 더 빠름. 
//주의점은 매크로는 함수가 아니라, 그대로 코드를 치환하는거라서

//완전히 다른 느낌이 될 수 있음...

//이렇게 하면 내가 해제를 신경쓰지 않아도 된다는 점
//반대로 이렇게 하면의 단점. 
//끝까지 끌고 나가야 하는 방식.

//SingleTon 매크로. 

#define SINGLE(type)public: \
					static type* GetInstance()\
					{\
						static type mgr;\
						return &mgr;\
					}

  1. CCore.h
#pragma once

//코어가 되는 객체는 하나만 있어야 하지 않을까?
//싱글톤 패턴. 객체의 생성을 1개로 제한. 

//어디서든 쉽게 접근 가능
//객체를 만들어주는 함수, 자기 스스로를 만들어주는 멤버 함수
/*
class CCore
{
	//정적 멤버(데이터 영역)
	//객체를 힙이나, 스택이나, 데이터에 어찌됐던 만듬.
	//데이터 영역에 하나만 있음. 데이터 멤버는 초기화를 반드시 해줘야함. 
	static CCore* g_pInst;
public:
	//멤버 함수를 호출하려면 객체를 호출해야 하는 것 아니냐? 

	//그래서 사용하는 건 static 함수.
	//스태틱은 객체가 있어도 없어도 호출 가능. 
	//객체로 static함수 써도 딱히 의미가 없음. 

	//static함수는, 멤버에 접근 불가(this키워드 불가) 
	// 그런데 유일하게 접근 가능한 것. 정적 멤버 가능. 
	// 정적 변수는 데이터 영역에 만들어짐. 
	//this란 호출한 객체를 의미하는 것. 
	static CCore* GetInstance() {

		if (nullptr == g_pInst) {
			g_pInst = new CCore;
		}

		return g_pInst;
	}

	static void Release() {
		if (nullptr != g_pInst) {
			delete g_pInst;
			g_pInst = nullptr;
		}
	}
private:
	//1번, 생성자 소멸자를 숨겨서 외부에서 못 만들도록.
	CCore();
	~CCore();
};

*/

//이 밑은 완전 새로운 방식임. 

#include "define.h"

class CCore {
	SINGLE(CCore);

private:
	HWND	m_hWnd;				//메인 윈도우 핸들. 
	POINT	m_ptResolution;		//메인 윈도우 해상도

public:
	int init(HWND _hWnd, POINT _ptResolution);

	void progress();

private:
	CCore();
	~CCore();

};
//CCore.cpp

#include "pch.h"
#include "CCore.h"

//CCore* CCore::g_pInst = nullptr;

CCore::CCore() {
}

CCore::~CCore() {

}

int CCore::init(HWND _hWnd, POINT _ptResolution) {
	m_hWnd = _hWnd;
	m_ptResolution = _ptResolution;

	//해상도에 맞게 윈도우 크기 조정. 

	return S_OK;
}

void CCore::progress() {
}
  1. CKeyMgr
//.cpp파일

#include "pch.h"
#include "CCore.h"

//CCore* CCore::g_pInst = nullptr;

CCore::CCore() {
}

CCore::~CCore() {

}

int CCore::init(HWND _hWnd, POINT _ptResolution) {
	m_hWnd = _hWnd;
	m_ptResolution = _ptResolution;

	//해상도에 맞게 윈도우 크기 조정. 

	return S_OK;
}

void CCore::progress() {
}
//.h파일

#pragma once

//#include 거는 거 불편함. 그래서. 
#include "define.h"

class CkeyMgr
{
	SINGLE(CkeyMgr);

};

  1. main.cpp파일
// Client.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//

#include "pch.h"
#include "framework.h"
#include "Client.h"
#include "CCore.h"

#define MAX_LOADSTRING 100
//WCHAR은 2바이트, wchar_t형임. (16비트)
//그거 이외엔 다른 게 없음(유니코드형을 위한 것)
// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
//메인함수가 처음 시작할 때 받아오는 값. 

HWND g_hWnd; //메인 윈도우 핸들. 
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.
//100길이로 그냥 선언해놓은 것. 

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
//함수들은 전방 선언해놓은 것.(함수 프로토타입)

#include <vector>

using std::vector;

struct tObjInfo {
    POINT g_ptObjectPos;
    POINT g_ptObjScale;
};

vector<tObjInfo> g_vecInfo;

//좌 상단
POINT g_ptLT;

//우 하단. 
POINT g_ptRB;
bool bLbtnDown = false;

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    //딱히 신경쓸 건 없음. 얘내 둘은 딱히 쓰이지 않는다... 라는 걸 선언한 느낌. 
    //매크로임. 
// TODO: 여기에 코드를 입력합니다.

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_CLIENT, szWindowClass, MAX_LOADSTRING);

    MyRegisterClass(hInstance);

    // 애플리케이션 초기화를 수행합니다:
    // 여기서 윈도우 창을 생성함. 유저와 프로그램 사이의 인터페이스, 접점이라는 의미도 됨.
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    //CCore 초기화
    //이게 메인 윈도우 입니다. 이게 해상도입니다. 
    if (FAILED(CCore::GetInstance()->init(g_hWnd, POINT{1280, 768}))) {
        MessageBox(nullptr, L"Core 객체 초기화 실패", L"ERROR", MB_OK);
        return FALSE;
    }

    //단축키 정보. 
    //리소스 뷰 -> Accelerator -> table 
    //특정 키와 조합했을 때, 키값. 
    //테이블 정보 불러와서. 
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CLIENT));

    MSG msg;

    while (true)
    {
        //무한 반복 -> 메시지가 없으면 if문 바깥으로 무시. 
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
            //메시지를 처리하기 전에, 

            if (WM_QUIT == msg.message) {
                break;
            }

            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        //메시지가 발생하지 않는 대부분의 시간. 
        else {
            //디자인 패턴(설계 유형)
            //싱글톤 패턴

            CCore::GetInstance()->progress();
        }
    }
    //두 번재 매개변수가 ID.

    return (int)msg.wParam;
}

//
//  함수: MyRegisterClass()
//
//  용도: 창 클래스를 등록합니다.
//  창 클래스를 우리가 정의하고, 등록하는 절차는 윈도우 OS에서 제공하는 함수. 
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
    //구조체 채워서 함수에 전달하기. 
    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    //WndProc이란?  전방 선언 되어 있는 위 함수. 
    //함수 포인터. 
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CLIENT));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    //처음 생성되는 메뉴바 null 값을 넣으면 메뉴바 사라짐. 
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_CLIENT);

    //이거의 Key값을 szWindowClass로 사용함. 이 MyRegisterClass의 Key값임. 
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    //이건 윈도우 OS에서 제공하는 함수임... 
    return RegisterClassExW(&wcex);
}

//
//   함수: InitInstance(HINSTANCE, int)
//
//   용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
//   주석:
//
//        이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
//        주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
    //szTitle 대신 "한재훈~" 넣어도 됨. 2번째 변수는 이름 말하는 거임. 
    //szWindowClass 키 값대로 만들어달라. 
    g_hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

    if (!g_hWnd)
    {
        return FALSE;
    }

    ShowWindow(g_hWnd, nCmdShow);
    UpdateWindow(g_hWnd);

    return TRUE;
}

//
//  함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  용도: 주 창의 메시지를 처리합니다.
//
//  WM_COMMAND  - 애플리케이션 메뉴를 처리합니다.
//  WM_PAINT    - 주 창을 그립니다.
//  WM_DESTROY  - 종료 메시지를 게시하고 반환합니다.
//
// 메시지가 발생한 윈도우 id값, 메시지등등이 같이 들어옴. 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    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); //Devicde Context.(그리기)

        HPEN hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));

        //자주 쓰는 펜, 자주 쓰는 오브젝트는 이미 다 만들어놨음. 
        HBRUSH hBlueBrush = CreateSolidBrush(RGB(0, 0, 255));

        HPEN hDefaultPen = static_cast<HPEN>(SelectObject(hdc, hRedPen));
        HBRUSH hDefaultBrush = static_cast<HBRUSH>(SelectObject(hdc, hBlueBrush));

        //3일차. 여러 개의 물체가 있다면???
        // 물체마다, 좌표가 달라서... 한 물체가 움직이면 다른 물체에게 영향을 미칠 수 있어서 전체적으로 재갱신을 해줘야만 함. 

        //게임이란 렌더링, 화면을 매 순간 적용하기 위해 화면을 계속 그리는 반복의 연속임.(프레임이라는 개념)

        //변경된 펜으로 사각형 그림. 
        if (bLbtnDown) {
            Rectangle(hdc, g_ptLT.x, g_ptLT.y, g_ptRB.x, g_ptRB.y);
        }

        //물체가 늘어나면 늘어날수록 화면 깜빡임이 되게 심해짐...
        //왜 그럴까? 마우스만 움직여도 InvalidateRect로 계속 다시 그림. 깜빡임 현상은 컴퓨터의 속도가 중요한 게 아님...

        //이건 속도의 문제가 아니라 타이밍의 문제임. 사람의 눈이 60프레임이라고 할때, 우리가 눈을 깜빡이는 0.16초 이후 0.01초 사이에 총알 지나가면 총알 못봄
        //우리는 그걸 그리는 중간 중간에 봐서 깜빡이는 것 처럼 보임

        //현재 1번 문제. 
        //그래서 우리는 픽셀 데이터(도화지)를 2개를 관리할 것임. 완성된 그림만을 보여주기 위함. 

        //현재 프로그램 2번 문제.
        //또 메시지가 존재하지 않으면 프로그램이 돌지 않는다는 문제도 존재함. 인공지능이나 AI가 움직이는 건 어떻게 하려고?
        //계속 계속 돌아가게 해줘야함. 

        // 우회 방법 -> 일정 시간마다 일부로 메시지를 발생시키는 방법이 있긴 한데
        // 정석 방법 -> 프로그램의 구조자체를 변경하는 것. 
        for (auto& info : g_vecInfo) {
            Rectangle(hdc,
                info.g_ptObjectPos.x - info.g_ptObjScale.x / 2,
                info.g_ptObjectPos.y - info.g_ptObjScale.y / 2,
                info.g_ptObjectPos.x + info.g_ptObjScale.x / 2,
                info.g_ptObjectPos.y + info.g_ptObjScale.y / 2);
        }

        //빨간 펜 다 썼으니, 기본 펜으로 다시 바꿔줌. 
        SelectObject(hdc, hDefaultPen);
        SelectObject(hdc, hDefaultBrush);

        //그리고 빨간 펜을 지워준다. 
        DeleteObject(hRedPen);
        DeleteObject(hBlueBrush);

        //그리기 종료. 
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_LBUTTONDOWN:
    {
        //마우스 왼쪽 누르기. 
        //4바이트 param인데, 2바이트 | 2바이트 이렇게 나눠서 비트마스킹... 비슷하게 해서 마우스 좌표를 보냄. 
        //코드보면 비트연산(<<, >>)으로 밀어서 가져옴. 
        //마우스의 x좌표, y좌표. 
        g_ptLT.x = LOWORD(lParam);
        g_ptLT.y = HIWORD(lParam);
        bLbtnDown = true;
    }
    break;
    case WM_MOUSEMOVE:
        g_ptRB.x = LOWORD(lParam);
        g_ptRB.y = HIWORD(lParam);
        //InvalidateRect(hWnd, nullptr, true);
        break;

    case WM_LBUTTONUP:
    {
        tObjInfo info = {};
        info.g_ptObjectPos.x = (g_ptLT.x + g_ptRB.x) / 2;
        info.g_ptObjectPos.y = (g_ptLT.y + g_ptRB.y) / 2;

        info.g_ptObjScale.x = abs(g_ptRB.x - g_ptLT.x);
        info.g_ptObjScale.y = abs(g_ptRB.y - g_ptLT.y);

        g_vecInfo.push_back(info);
        bLbtnDown = false;
    }
    break;

    case WM_TIMER:
    {
        int a = 0;
    }
    break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_KEYDOWN:
    {
        //키가 눌리는 걸 감지하는 것. 
        //무슨 키냐, 라는 걸 알아야 하지 않나? 
        switch (wParam) {
        case VK_UP:
            //위쪽 화살표. 
            //g_ptObjectPos.y -= 10;
            //누를때마다 화면 갱신을 해줘야지. 
            // 이렇게 하면, 기존에 그려진 부분은 그대로 남아있슴... 그래서 기존에 있던 걸 지워줘야함. 
            //InvalidateRect(hWnd, nullptr, false);
            //true로 바꾸면, 기존에 그려져 있던 픽셀들 전부 깔끔하게 청소함. 
            InvalidateRect(hWnd, nullptr, true);

            break;
        case VK_DOWN:
            //g_ptObjectPos.y += 10;
            InvalidateRect(hWnd, nullptr, true);
            break;
        case VK_LEFT:
            //g_ptObjectPos.x -= 10;
            InvalidateRect(hWnd, nullptr, true);
            break;
        case VK_RIGHT:
            //g_ptObjectPos.x += 10;
            InvalidateRect(hWnd, nullptr, true);
            break;

            //꾹 누르고 있으면 한템포 느림... 
            //KEYDOWN은 한 번 눌리고, 어느정도 딜레이 이후에 계속 들어오는 방식이라 그럼.

        case 'W':
        {
            int a = 0;
        }
        //일반 키. 대문자 기준으로만 됨. 
        break;
        }
    }
    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;
}

  • 엔진의 코어는 단 하나만 있어야 한다. 한 번 생성된 이후에 다른 곳에서 생성되지 않도록, 오직 하나만이 존재해야 하는 디자인 패턴을 Singleton패턴이라고 한다.
  • 이 패턴은 Manager등에서 많이 쓰인다.

WINAPI 카테고리 내 다른 글 보러가기

댓글 남기기