Win32 창 생성 과정을 단계별로 이해하기
Win32 API를 처음 따라 만들다 보면 이상한 느낌이 드는 순간이 있다. RegisterClassEx()로 등록하고, CreateWindowEx()로 생성하고, ShowWindow()까지 호출한다. 창을 만드는 함수 하나만 있으면 될 것 같은데 실제 코드는 여러 단계로 나뉘어 있다.
이 구조는 단순히 함수가 많아서 복잡한 것이 아니다. Windows 내부에서 창 생성 과정이 분리되어 있기 때문이다. CreateWindowEx()는 그 과정의 중심 역할을 담당한다.
실제 학습에서는 먼저 하나만 기억하면 된다.
CreateWindowEx()는 등록된 창 클래스를 기반으로 Windows에게 새로운 윈도우 객체 생성을 요청하는 함수다.
CreateWindowEx는 실제 창을 만드는 함수다
CreateWindowEx()는 등록된 창 클래스 정보를 사용해 창 객체를 생성한다.
함수 원형은 다음과 같다.
HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
매개변수는 많아 보이지만 실제 역할은 몇 가지로 압축된다.
- 어떤 창 클래스를 사용할 것인가
- 창 제목은 무엇인가
- 크기와 위치는 어디인가
- 부모 창이 존재하는가
- 추가 데이터를 전달할 것인가
성공하면 반환값으로 HWND를 받는다.
HWND는 Windows 내부에서 관리하는 창 핸들이다. 이후 창 이동, 크기 변경, 메시지 전송 등 대부분 작업은 이 핸들을 기준으로 이루어진다.
또 하나 자주 나오는 질문이 있다.
Ex는 무엇인가?
여기서 Ex는 Extended(확장)의 의미다. CreateWindow()보다 추가적인 확장 스타일을 지정할 수 있도록 만든 버전이다.
STEP 1 — 먼저 창 클래스가 등록되어 있어야 한다
CreateWindowEx()는 아무 정보 없이 창을 생성하지 않는다.
먼저 창 설계도가 등록되어 있어야 한다.
WNDCLASSEX wc={0};
wc.cbSize=sizeof(WNDCLASSEX);
wc.lpfnWndProc=WndProc;
wc.hInstance=hInstance;
wc.lpszClassName=TEXT("MyWindow");
RegisterClassEx(&wc);
이 단계에서 Windows는 다음 정보를 내부에 저장한다.
- 메시지 처리 함수
- 클래스 이름
- 아이콘
- 커서
- 배경 정보
등록 작업이 없다면 어떤 형태의 창을 만들어야 하는지 알 수 없다.
STEP 2 — CreateWindowEx 매개변수는 어떤 역할을 하는가
실제 코드에서 가장 많이 사용하는 형태는 다음과 비슷하다.
CreateWindowEx(
0,
TEXT("MyWindow"),
TEXT("Sample Window"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
800,
600,
NULL,
NULL,
hInstance,
NULL
);
처음에는 모든 값을 외울 필요가 없다.
핵심 매개변수 몇 개만 이해해도 충분하다.
| 매개변수 | 역할 |
|---|---|
| lpClassName | 등록된 창 클래스 이름 |
| lpWindowName | 창 제목 |
| dwStyle | 창 모양과 속성 |
| nWidth / nHeight | 창 크기 |
| hInstance | 프로그램 인스턴스 |
STEP 3 — Ex가 붙는 이유는 확장 스타일 때문이다
CreateWindowEx()가 CreateWindow()와 다른 가장 큰 이유는 dwExStyle 매개변수다.
일부 확장 스타일 예시는 다음과 같다.
| 확장 스타일 | 역할 |
|---|---|
| WS_EX_TOPMOST | 항상 위에 표시 |
| WS_EX_TOOLWINDOW | 도구 창 형태 생성 |
| WS_EX_LAYERED | 투명 효과 지원 |
간단한 예제에서는 0을 사용하는 경우가 많다.
하지만 실제 프로그램에서는 이 옵션을 자주 활용한다.
STEP 4 — Windows는 등록된 창 클래스를 검색한다
함수가 호출되면 Windows는 클래스 이름을 찾는다.
동작 흐름은 다음과 비슷하다.
- CreateWindowEx 호출
- 클래스 이름 검색
- 등록 정보 확인
- 윈도우 객체 생성
- HWND 반환
이 때문에 클래스 이름 오타가 발생하면 생성이 실패할 수 있다.
wc.lpszClassName=TEXT("MyWindow");
CreateWindowEx(
0,
TEXT("Window"),
...
);
문자 하나만 달라도 Windows는 다른 클래스로 인식한다.
STEP 5 — 창 객체 생성 후 초기 메시지가 전달된다
생성 직후 Windows는 여러 메시지를 자동으로 전달한다.
일반적인 흐름은 다음과 같다.
- WM_NCCREATE
- WM_NCCALCSIZE
- WM_CREATE
WM_CREATE 시점에서는 버튼 생성, 초기 데이터 설정, 컨트롤 연결 작업이 자주 이루어진다.
STEP 6 — ShowWindow 이후 화면에 표시된다
중요한 점 하나가 있다.
CreateWindowEx()가 끝났다고 곧바로 화면에 표시되는 것은 아니다.
창 객체 생성과 화면 표시 과정은 분리되어 있다.
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
그 이후 메시지 루프가 시작된다.
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
여기서부터 마우스 클릭, 키 입력, 창 이동 같은 이벤트가 처리된다.
실제 창 생성 과정을 한 줄로 정리하면 다음과 같다.
창 클래스 등록 → CreateWindowEx 호출 → HWND 생성 → 초기 메시지 전달 → ShowWindow → 메시지 루프 실행
Win32 입문자가 CreateWindowEx()에서 오류가 발생하면 함수 자체 문제라고 생각하는 경우가 많다. 하지만 실제로는 RegisterClassEx() 누락이나 클래스 이름 오타가 원인인 경우가 훨씬 많다.
CreateWindowEx()가 NULL을 반환했다면 GetLastError()를 사용해 원인을 확인하는 습관이 도움이 된다.