WinAPI 입문기 하나하나 배워보자

WinAPI 첫 걸음: Hello World 윈도우 만들기

WinAPI에서 윈도우를 만들려면 “클래스 등록 → 윈도우 생성 → 메시지 처리”라는 흐름을 반드시 따라야 합니다. 이 구조만 이해하면 처음 보는 코드도 훨씬 쉽게 읽히게 됩니다.
처음 Visual Studio에서 WinAPI 프로젝트를 실행해 보면, 창이 보이지 않거나 잠깐 나타났다 바로 종료되는 경우를 자주 겪게 됩니다. 콘솔 프로그램과 달리 구조가 눈에 보이지 않기 때문입니다. 이 글에서는 실제로 “Hello World”가 화면에 출력되는 윈도우를 단계별로 만들어 보겠습니다.

WinAPI 프로그램의 기본 구조 이해하기

WinAPI 프로그램은 main이 아닌 WinMain에서 시작됩니다. 그리고 일반적인 함수 호출 흐름이 아니라, “메시지 기반 구조”로 동작합니다.

핵심 흐름은 다음 세 단계로 이어집니다.

  • 윈도우 클래스 등록 (설계도 정의)
  • 윈도우 생성 (실제 창 생성)
  • 메시지 처리 (동작 정의)

이 구조를 이해하면 코드가 왜 복잡해 보이는지도 자연스럽게 납득할 수 있습니다. 특히 클래스를 등록하지 않으면 윈도우를 생성할 수 없습니다.

STEP 1 – 윈도우 클래스 등록하기

윈도우를 만들기 위해서는 먼저 WNDCLASS 구조체를 통해 “창의 성격”을 정의해야 합니다.

핵심 요소는 다음과 같습니다.

  1. lpfnWndProc: 메시지를 처리할 함수
  2. hInstance: 프로그램 인스턴스
  3. lpszClassName: 클래스 이름
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"HelloWindow";

RegisterClass(&wc);

여기서 L 접두어는 UNICODE 문자열을 의미합니다. WinAPI는 기본적으로 유니코드를 사용하기 때문에 반드시 붙여주는 것이 안전합니다.
자주 발생하는 실수는 RegisterClass 실패를 확인하지 않는 것입니다. 이 경우 이후 단계도 모두 실패하게 됩니다.

STEP 2 – 실제 윈도우 생성 및 표시

클래스를 등록했다면 이제 실제 창을 생성합니다.

HWND hwnd = CreateWindow(
    L"HelloWindow",
    L"Hello World Window",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    500, 300,
    NULL, NULL, hInstance, NULL
);

ShowWindow(hwnd, nCmdShow);

CreateWindow는 창의 크기, 위치, 스타일을 결정합니다. 그리고 ShowWindow를 호출해야 화면에 표시됩니다.
이 단계에서 가장 흔한 실수는 ShowWindow를 빠뜨리는 것입니다. 실행은 되지만 창이 보이지 않는 원인이 됩니다.

STEP 3 – 메시지 처리와 Hello World 출력

이제 창은 만들어졌지만, 아직 내용은 비어 있습니다. 화면에 무엇을 그릴지는 직접 정의해야 합니다.

WndProc 함수에서 WM_PAINT 메시지를 처리하면 됩니다.

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        TextOut(hdc, 100, 100, L"Hello World", 11);

        EndPaint(hwnd, &ps);
    }
    return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

WM_PAINT는 화면을 다시 그릴 때 호출되는 메시지입니다. 이 안에서 TextOut을 사용하면 문자열을 출력할 수 있습니다.

마지막으로 메시지 루프가 필요합니다.

  • 메시지를 받아야 창이 유지됩니다
  • 루프가 없으면 프로그램이 바로 종료됩니다
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

이 구조까지 완성하면 WinAPI의 기본 흐름이 모두 연결됩니다.
윈도우 생성 → 메시지 처리 → 화면 출력까지 이어지며, 정상적인 GUI 프로그램이 동작하게 됩니다.