본문 바로가기
개발개발/WinAPI & DirectX

윈도우 창 생성과 windows.cpp 파일 분석

by 유잉유잉유잉 2025. 2. 1.
728x90


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가 존재할 수 있습니다.
    • 고유한 번호는 임의로 발급할 수 없고, 운영체제만 발급할 수 있습니다.
// 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() 츨라이언트 영역을 강제로 다시 그리게 요청해주는 함수입니다. 처음 생성시나 특정 상황에 창을 새로고침 해야할 경우 사용합니다. 

 

다시 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;
}

 

 

⭐ 메세지 기반의 운영체제 동작방식은 잘 이해하고 있어야합니다. 

기본코드를 건들일은 많이 있지 않지만, 일반적인 방법으로 처리되지않고 직접 만져야 하는 경우가 간혹있기 때문입니다.

 

728x90

댓글