본문 바로가기

Win32 API

7. Win32 API - Windows 프로그램 구성

 

1. Windows 데스크톱 프로그램의 기본 구성

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

#include "pch.h"
#include "Client.h"

#define MAX_LOADSTRING 100

// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
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);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // 생략 ...
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    // 생략 ...
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   // 생략 ...
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // 생략...
}

// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    // 생략...
}

Windows 데스크톱 프로그래밍을 처음 생성했을 떄의 구성요소를 확인할 수 있습니다.
wWinMain : Window 프로그램의 진입점
MyRegiesrClass : Window Class 등록함수
InitInstance : Instance 핸들을 전역변수hInst에 저장합니다.
WndProc : 프로그래머가 윈도우 메시지를 어떻게 처리할 지에 대해서 정의할 수 있습니다. 다만 이미 인자 형태와 반환 값의 형태가 지정되어 있는 형태의 함수입니다.
About : 정보 대화상자의 메시지 처리기입니다.



WinMain

기본 구성

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

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CLIENT));

    MSG msg;

    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

Win32 프로그램의 시작 함수입니다.

 

매개 변수

  • hInstance : 이 프로그램의 instance 핸들 값이 전달됩니다.
  • hPrevInstance : 현재 사용되지 않는 매개변수 (항상 NULL입니다.)
  • lpCmdLine : main 함수의 argc, argv 인자처럼 실행 인자가 전달 됨. main 함수처럼 인자들이 배열로 하나씩 나누어져서 전달되는 것이 아니라 하나의 문자열로 전달됩니다. ⇒ 실행 파일은 포함되지 않습니다.
    • 예시로 자신의 프로그램이 soft.exe이고 실행창에 soft.exe Hello soft 라고 입력하면 lpCmdLine에는 Hello soft 라는 문자열이 전달됩니다.
  • nCmdShow : 응용 프로그램의 초기 시작 형식이 전달됩니다. 보통의 경우 SW_SHOWDEFAULT 값이 전달되며 사용자가 단축 아이콘이나 다른 응용 프로그램에서 특정 값을 지정하면 해당 값이 이 매개변수로 전달됩니다.

 

함수의 구성

  • 클래스 등록
  • 윈도우 생성
  • 메시지 루프
  • 메시지 처리

 

클래스 등록

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // ... 생략

    MyRegisterClass(hInstance);

    // ... 생략
}

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_CLIENT));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_CLIENT);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

윈도우 클래스를 등록하는 부분입니다.
각 Window 단위로 실행 명령어에 대한 중복을 줄이기 위한 개념이 윈도우 클래스이고 해당 클래스의 구조체를 정의 한뒤 RegisterClass 함수를 이용하여등록하는 부분입니다.

 

윈도우 생성

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // ... 생략

    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    // ... 생략
}

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

인스턴스 핸들을 전역 변수에 저장합니다
CreateWindowW 함수를 사용하여 프로그램에서 사용할 윈도우를 생성합니다.

 

메시지 루프

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // ... 생략

    SG msg;

    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    // ... 생략
}

Win32 응용 프로그램은 운영체제 또는 다른 응용 프로그램과 메시지를 주고받는 형식으로 진행됩니다.
응용 프로그램은 메시지를 수신할 수 있도록 메시지큐를 가지고 있습니다.

 

MSG 구조체

typedef struct tagMSG {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
}

메시지 큐에 전달된 메시지를 읽어올 때 사용하는 구조체입니다.

 

GetMessage 함수

  • 메시지 큐에서 메시지를 읽을 때 사용하는 함수, 보통의 경우에는 0이 아닌 값을 반환합니다.
    하지만 메시지 중에서 WM_QUIT 메시지가 수신되어 GetMessage 함수로 읽게 되면 0 값을 반환하여 while 반복문이 종료하게 됩니다.
  • 메시지 처리를 수행하던 while 반복문이 종료된다는 뜻은 WinMain 함수가 종료된다는 뜻이고 프로그램이 종료된다는 것을 의미합니다.
  • 자신의 응용프로그램에 WM_QUIT 메시지를 전달해서 프로그램을 종료하고 싶다면 PostQuitMessage 함수를 사용하면 됩니다.


TranslateMessage 함수

  • 현재 수신된 메시지가 WM_KEYDOWN과 같은 가상키와 관련된 메시지이면 키보드 배열과 관련된 값이 아닌 ASCII 값으로 해석된 WM_CHAR와 같은 추가 메시지가 발생합니다.


DispatchMessage 함수

  • 현재 수신된 메시지를 처리하는 함수. 호출 시 해당 메시지를 처리하기 위해 내부적으로 사전작업이 진행됩니다.
  • 사용자의 메시지 처리기인 WndProc 함수 호출.
  • WndProc 함수에서 메시지 별로 사용자가 원하는 작업을 구성하면 됩니다.
  • DispatchMessage 함수 내부에서 사용자가 만든 WndProc 함수를 호출할 수 있는 이유는 Window Class 등록 시 lpfnWndProc에 WndProc함수의 주소를 대입하였기 때문에 호출 가능합니다.

 

메시지 처리

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);
            // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Window Procedure를 사용자가 재정의한 함수입니다.
CALLBACK 형태를 따르기 때문에 함수의 원형은 매개변수와 반환형을 사용자가 변경할 수 없지만 함수의 이름이나, 내부 구현은 변경이 가능합니다.

해당 함수에서는 사용자가 처리하고 싶은 메시지만 처리하면 됩니다.
필수적으로 처리하면 안되는 메시지를 놓쳐선 안되기 때문에 DefWindowProc 함수를 호출함으로써 내부적으로 정해진 작업을 수행합니다.

 

참조

'Win32 API' 카테고리의 다른 글

9. Win32 API - GDI 개요  (0) 2024.02.24
8. Win32 API - 윈도우의 생성과 소멸  (0) 2024.02.24
6. Win32 API - WindowClass  (0) 2024.02.24
5. Win32 API - Event  (0) 2024.02.24
4. Win32 API - Message  (0) 2024.02.19