WinMain 함수 완벽 이해: Win32 프로그램은 어디서 시작될까?

Visual Studio에서 Win32 프로젝트를 처음 생성하면 main() 대신 낯선 함수 하나가 등장한다.

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
)

처음 보면 함수 이름도 길고 매개변수도 복잡하다. 특히 C/C++만 공부했던 입문자 입장에서는 “왜 일반 main 함수가 아닌가?”라는 의문이 자연스럽게 생긴다.

실제로 사용자가 exe 파일을 실행하면 Windows Loader가 프로그램을 메모리에 적재하고, 이후 운영체제가 WinMain 함수를 호출한다. 즉 WinMain은 단순 시작 함수가 아니라, Windows GUI 프로그램이 처음 실행되는 진입 지점 역할을 한다.

WinMain을 처음 보면 왜 main 함수와 다르게 생겼을까?

main()은 콘솔 프로그램의 시작점이다. 반면 WinMain은 Windows GUI 프로그램의 시작점이다.

GUI 프로그램은 단순 코드 실행만 수행하는 것이 아니라 창 생성, 메시지 처리, 이벤트 관리까지 담당해야 한다. 그래서 Windows는 프로그램 시작 시 추가 정보들을 WinMain 매개변수로 전달한다.

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
)

즉 WinMain은 운영체제와 연결된 GUI 프로그램 구조 자체를 시작하는 함수에 가깝다.

WinMain은 Windows GUI 프로그램의 시작점이다

핵심부터 정리하면 WinMain은 Windows GUI 애플리케이션이 실행될 때 가장 먼저 호출되는 함수다.

운영체제는 프로그램 실행 후 WinMain을 호출하고, 프로그램은 이 함수 안에서 창 생성과 메시지 루프 같은 핵심 초기화를 진행한다.

단계 역할
1 윈도우 클래스 등록
2 창 생성
3 창 표시
4 메시지 루프 실행

즉 WinMain은 Win32 프로그램 전체 흐름의 출발점 역할을 한다.

콘솔 프로그램과 GUI 프로그램의 차이

콘솔 프로그램은 비교적 단순하다.

사용자가 프로그램을 실행하면 main() 함수가 시작되고, 코드가 순차적으로 실행된 뒤 종료된다.

하지만 GUI 프로그램은 다르다.

창이 계속 살아 있어야 하고, 사용자 입력도 실시간으로 처리해야 한다. 버튼 클릭, 키보드 입력, 창 이동 같은 이벤트도 모두 처리해야 한다.

그래서 Windows는 GUI 프로그램을 이벤트 기반 구조로 동작시킨다.

WinMain 함수 선언을 한 줄씩 해석해보기

처음에는 복잡해 보이지만, WinMain 매개변수는 각각 역할이 명확하다.

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
)

hInstance는 무엇인가?

hInstance는 현재 실행 중인 프로그램 인스턴스를 가리키는 핸들이다.

이 값은 주로 다음 작업에서 사용된다.

  • 윈도우 클래스 등록
  • 리소스 로딩
  • 아이콘 연결
  • CreateWindowEx 호출

예를 들어 윈도우 클래스를 등록할 때도 hInstance가 사용된다.

wc.hInstance = hInstance;

lpCmdLine은 언제 사용될까?

lpCmdLine은 프로그램 실행 시 전달된 명령줄 문자열이다.

예를 들어 다음처럼 실행하면:

MyApp.exe test.txt

test.txt 부분이 lpCmdLine으로 전달된다.

초보 단계에서는 자주 사용되지는 않지만, 파일 경로나 실행 옵션을 전달할 때 활용된다.

nCmdShow는 왜 필요한가?

nCmdShow는 프로그램 창을 어떤 상태로 표시할지 결정하는 값이다.

ShowWindow(hwnd, nCmdShow);

이 값에 따라 창이 최소화 상태로 시작될 수도 있고, 일반 창으로 표시될 수도 있다.

WINAPI는 무엇인가?

많은 입문자가 함수 이름만 보고 넘어가지만, WINAPI 역시 중요한 의미를 가진다.

int WINAPI WinMain(...)

여기서 WINAPI는 Windows API 호출 규약(call convention)을 의미한다.

쉽게 말하면 함수 인자를 어떤 방식으로 전달하고 정리할지를 결정하는 규칙이다.

WinMain 안에서 가장 먼저 일어나는 일

Win32 프로그램은 WinMain이 시작되자마자 바로 창을 만드는 것이 아니다.

보통 가장 먼저 수행하는 작업은 윈도우 클래스(Window Class) 등록이다.

WNDCLASS wc = {};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"MyWindowClass";

RegisterClass(&wc);

여기서 말하는 윈도우 클래스는 C++ 클래스가 아니라 “창 설계 정보”에 가깝다.

운영체제는 창 생성 전에 다음 정보를 알아야 한다.

  1. 어떤 함수가 메시지를 처리하는가
  2. 어떤 이름으로 창을 생성할 것인가
  3. 어떤 스타일을 사용할 것인가
  4. 어떤 아이콘과 리소스를 연결할 것인가

즉 RegisterClass 과정은 “창 생성 전 초기 설정 등록 단계”라고 이해하면 된다.

윈도우 클래스 등록 과정 이해하기

초보 단계에서는 RegisterClass가 불필요하게 느껴질 수 있다.

하지만 Windows 입장에서는 창을 생성하기 전에 설계 정보를 먼저 등록받아야 한다.

이후 CreateWindowEx는 등록된 클래스 이름을 기반으로 실제 창을 생성한다.

CreateWindowEx(
    0,
    L"MyWindowClass",
    L"Hello",
    WS_OVERLAPPEDWINDOW,
    ...
);

즉 Win32 창 생성은 다음 흐름으로 연결된다.

  • 창 설계 정보 등록
  • 등록된 이름 기반으로 창 생성
  • HWND 반환
  • 메시지 처리 시작

CreateWindowEx 이후 프로그램 흐름 이해하기

윈도우 클래스 등록이 끝나면 실제 창 생성이 진행된다.

HWND hwnd = CreateWindowEx(...);

이후 프로그램은 HWND를 이용해 창을 표시한다.

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

Win32를 처음 공부할 때 자주 발생하는 실수 중 하나가 바로 ShowWindow 호출을 빼먹는 것이다.

CreateWindowEx 호출이 성공했더라도 ShowWindow를 호출하지 않으면 화면에 창이 보이지 않을 수 있다. 그래서 초보 단계에서는 “창 생성이 실패했다”고 착각하는 경우도 자주 발생한다.

창 생성과 HWND 연결 구조

Windows 운영체제는 수많은 창을 동시에 관리한다.

따라서 어떤 창에 어떤 작업을 수행해야 하는지 식별할 기준이 필요하다. 그 역할을 담당하는 것이 HWND다.

버튼 클릭, 키보드 입력, 창 이동 같은 이벤트 역시 모두 HWND 기준으로 전달된다.

즉 CreateWindowEx 이후부터 프로그램은 HWND 중심 구조로 동작하게 된다.

메시지 루프는 왜 WinMain 안에 존재할까?

Win32 구조에서 가장 중요한 부분 중 하나가 메시지 루프(Message Loop)다.

대표적인 형태는 다음과 같다.

MSG msg;

while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

실제로 Win32 프로그램은 이 루프 안에서 대부분의 시간을 보낸다.

Windows는 사용자 입력이 발생할 때마다 메시지를 생성한다. 프로그램은 GetMessage를 통해 메시지를 받아오고, DispatchMessage를 통해 실제 Window Procedure로 전달한다.

처음 Win32를 공부할 때 메시지 루프를 제거하면 창이 생성되자마자 프로그램이 즉시 종료되는 경험을 하게 된다. 이 과정을 직접 겪어보면 Win32가 순차 실행 기반이 아니라 이벤트 기반 구조라는 점이 훨씬 명확하게 느껴진다.

GetMessage와 DispatchMessage의 역할

메시지 루프 흐름은 아래 구조로 이해하면 쉽다.

단계 설명
사용자 입력 발생 마우스/키보드 이벤트 생성
Windows 메시지 생성 운영체제가 메시지 큐 저장
GetMessage 메시지 수신
DispatchMessage 실제 창으로 전달
WndProc 이벤트 처리

실제 Win32 GUI 프로그램 대부분은 이 메시지 흐름을 기반으로 동작한다.

WinMain 하나로 Win32 프로그램 흐름 이해하기

처음에는 WinMain 함수 하나가 지나치게 복잡하게 느껴질 수 있다.

하지만 전체 흐름을 보면 구조는 생각보다 단순하다.

  1. 프로그램 시작
  2. 윈도우 클래스 등록
  3. 창 생성
  4. 창 표시
  5. 메시지 루프 실행
  6. 사용자 입력 처리

즉 WinMain은 “Windows GUI 프로그램 전체 시작 흐름”을 담고 있는 함수다.

이벤트 기반 프로그램 구조 이해하기

콘솔 프로그램은 순차 실행 기반 구조다.

하지만 Win32 GUI 프로그램은 이벤트 기반 구조다.

사용자 입력이 발생할 때마다 운영체제가 메시지를 보내고, 프로그램은 그 메시지에 반응한다.

흥미로운 점은 일반 Win32 프로그램과 게임 프로그램의 루프 구조가 다를 수 있다는 것이다.

  • 일반 Win32 프로그램 → GetMessage 기반 메시지 루프
  • 게임 프로그램 → PeekMessage 기반 프레임 루프 사용 가능

즉 Win32 메시지 시스템은 단순 UI 처리 구조를 넘어, Windows 애플리케이션 전체의 기반 시스템이라고 볼 수 있다.

그리고 WinMain은 바로 그 시스템의 출발점 역할을 한다.