Win32 API를 처음 공부하면 가장 자주 보게 되는 타입 중 하나가 HWND다. 버튼을 만들 때도 등장하고, 메시지를 처리할 때도 등장하며, 창을 화면에 띄울 때도 계속 사용된다. 그런데 초보자 입장에서는 이 값이 정확히 무엇인지 쉽게 감이 오지 않는다. 변수처럼 보이기도 하고, 객체 주소처럼 느껴지기도 하며, 어떤 경우에는 단순 ID처럼 보이기도 한다.
Win32 API를 제대로 이해하려면 결국 HWND의 정체를 정확하게 이해해야 한다. HWND는 창 자체가 아니라, 운영체제가 관리하는 창(Window)을 식별하기 위한 핸들(handle)이다.
HWND를 처음 보면 왜 헷갈릴까?
C/C++를 처음 배우는 단계에서는 HWND가 상당히 애매하게 느껴진다. 코드에서는 포인터처럼 보이고 함수 인자로 계속 전달되는데, 정작 내부 구조는 보이지 않는다. Visual Studio 자동완성에서도 HWND hwnd 같은 코드가 반복적으로 등장하기 때문에 “이게 정확히 어떤 데이터인가?”라는 의문이 자연스럽게 생긴다.
HWND hwnd;
실제로 많은 입문자가 HWND를 “창 객체 자체”라고 이해한다. 하지만 Win32 구조에서는 창과 HWND가 같은 개념이 아니다. 창은 운영체제가 내부적으로 관리하는 리소스이고, HWND는 그 리소스를 가리키기 위한 핸들 값이다.
이 개념이 처음 어렵게 느껴지는 이유는 현대 프레임워크와 구조 차이가 크기 때문이다. 예를 들어 C#, Qt, Unreal Engine 같은 환경에서는 객체 인스턴스를 직접 다루는 경우가 많다. 반면 Win32 API는 운영체제가 중심이 되는 구조이며, 프로그램은 핸들을 통해 시스템 리소스를 간접적으로 제어한다.
HWND는 창 자체가 아니라 창을 가리키는 핸들이다
핵심부터 정리하면 HWND는 창(Window) 자체가 아니다. 운영체제가 생성한 창을 식별하기 위한 핸들(handle)이다.
핸들은 운영체제가 관리하는 리소스에 접근하기 위한 간접 참조값이다. 파일 핸들, 프로세스 핸들, 스레드 핸들 역시 같은 개념으로 동작한다. Windows는 내부 데이터를 직접 노출하지 않고, 대신 핸들을 제공한다.
예를 들어 다음과 같은 코드가 있다고 가정해보자.
HWND hwnd = CreateWindowEx(
0,
L"MyWindowClass",
L"Hello",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
800,
600,
NULL,
NULL,
hInstance,
NULL
);
여기서 CreateWindowEx는 실제 창을 생성한 뒤 HWND를 반환한다. 이 값은 “새로 만들어진 창을 제어하기 위한 식별자” 역할을 한다.
이후 프로그램은 이 HWND를 사용해 창을 화면에 표시하거나 위치를 변경한다.
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
이 방식 덕분에 Windows는 내부 구조를 직접 노출하지 않으면서도 안정적으로 리소스를 관리할 수 있다.
| 구분 | 의미 |
|---|---|
| Window | 운영체제가 관리하는 실제 창 객체 |
| HWND | 해당 창을 식별하기 위한 핸들 |
| CreateWindowEx | 창 생성 후 HWND 반환 |
| ShowWindow | HWND를 이용해 창 표시 |
핸들이라는 개념을 먼저 이해해야 하는 이유
Win32 API는 대부분 핸들 기반으로 설계되어 있다. HWND를 이해하지 못하면 이후 등장하는 HDC, HINSTANCE, HANDLE 같은 타입도 계속 헷갈리게 된다.
예를 들어 HDC는 Device Context Handle이다. 화면에 그림을 그릴 때 사용하는 핸들이다. HINSTANCE는 실행 중인 프로그램 인스턴스를 가리키는 핸들이다.
즉 Win32는 거의 모든 시스템 리소스를 “핸들” 형태로 다룬다.
Windows는 왜 HWND로 창을 구분할까?
Windows 운영체제는 동시에 수많은 창을 관리한다. 메모장, 브라우저, 탐색기, 게임, IDE까지 모두 각각의 창을 가진다.
운영체제 입장에서는 “어떤 창에 어떤 작업을 수행해야 하는가”를 정확하게 구분할 필요가 있다. 이때 사용하는 식별자가 HWND다.
예를 들어 사용자가 창을 클릭하면 Windows는 해당 창의 HWND를 기준으로 메시지를 전달한다. 버튼 클릭, 키보드 입력, 마우스 이동 같은 이벤트 역시 모두 특정 HWND와 연결된다.
즉 HWND는 단순 식별자를 넘어, 운영체제와 프로그램 사이에서 창을 연결하는 기준 역할을 한다.
운영체제, 창 객체, 애플리케이션의 관계
Win32 구조를 이해하려면 역할 분리를 명확히 볼 필요가 있다.
- 프로그램이
CreateWindowEx호출 - Windows가 내부 창 객체 생성
- HWND 반환
- 프로그램이 HWND로 창 제어
- 운영체제가 메시지 전달
예를 들어 WinMain 함수 내부에서는 보통 다음과 같은 흐름으로 HWND가 사용된다.
HWND hwnd = CreateWindowEx(...);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
이후 메시지 루프가 실행되면서 운영체제는 계속 HWND를 기준으로 메시지를 전달한다.
즉 HWND는 애플리케이션이 시스템 창에 접근하기 위한 “출입증” 같은 역할이라고 볼 수 있다.
CreateWindowEx와 HWND의 연결 구조
Win32에서 HWND를 가장 자주 처음 접하는 위치는 CreateWindowEx 함수다.
이 함수는 새로운 윈도우를 생성하는 역할을 한다. 버튼, 에디트 박스, 메인 윈도우 역시 내부적으로는 모두 Window 구조다.
함수 호출이 성공하면 반환값으로 HWND가 전달된다.
HWND hwnd = CreateWindowEx(...);
Windows 운영체제가 새로운 창 리소스를 만들고, 그 창을 식별할 수 있는 핸들을 프로그램에게 넘겨준 것이다.
이후 거의 모든 작업은 HWND를 기준으로 진행된다.
SetWindowText(hwnd, L"New Title");
MoveWindow(hwnd, 100, 100, 500, 400, TRUE);
DestroyWindow(hwnd);
함수 이름은 달라도 공통점이 있다. 모두 HWND를 첫 번째 인자로 받는다.
즉 Win32 API는 “어떤 창을 제어할 것인가”를 HWND를 통해 결정한다.
메시지 처리에서 HWND가 중요한 이유
Win32 API의 핵심은 메시지 기반 구조다.
사용자가 키보드를 누르거나 마우스를 움직이면 Windows는 해당 이벤트를 메시지 형태로 전달한다. 그리고 어떤 창이 메시지를 받아야 하는지 HWND를 기준으로 판단한다.
대표적인 함수가 Window Procedure다.
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam
)
여기서 첫 번째 인자가 HWND다. 즉 현재 메시지를 받고 있는 창이 누구인지 알려주는 역할이다.
만약 프로그램 안에 여러 개의 창이나 버튼이 존재한다면 HWND가 없으면 구분 자체가 불가능하다.
- 버튼 클릭 메시지 처리
- 창 크기 변경 이벤트 처리
- 특정 윈도우 종료 처리
- 키보드 입력 대상 구분
이 모든 동작이 HWND 기준으로 연결된다.
어떤 창이 어떤 메시지를 받았는지 구분하는 기준
여러 버튼을 만든 뒤 각각의 HWND 값을 출력해보면 서로 다른 핸들이 생성되는 것을 확인할 수 있다.
실제로 Win32를 처음 공부할 때 버튼 클릭이 정상적으로 동작하지 않는 경우가 자주 발생한다. 대부분은 HWND 구분을 제대로 이해하지 못한 상태에서 메시지를 처리하기 때문이다.
이 구조를 이해하면 이후 WM_COMMAND, SendMessage, PostMessage 같은 API도 훨씬 쉽게 연결된다.
HWND를 객체처럼 보면 헷갈리는 이유
C++ 객체지향 프로그래밍에 익숙한 사람일수록 HWND 개념에서 혼란을 겪는 경우가 많다.
왜냐하면 HWND는 클래스 인스턴스처럼 직접 접근 가능한 객체가 아니기 때문이다.
예를 들어 다음 같은 코드는 불가능하다.
hwnd->title = "Hello";
HWND는 객체 포인터가 아니라 운영체제가 관리하는 핸들 값이다. 따라서 반드시 Win32 API 함수를 통해 간접적으로 접근해야 한다.
흥미로운 점은 현대 엔진과 프레임워크도 결국 Windows 환경에서는 내부적으로 HWND 기반 구조 위에서 동작한다는 것이다. Unreal Engine이나 Unity Editor 역시 Windows 운영체제와 통신할 때는 HWND를 사용한다.
즉 Win32 API는 오래된 기술이지만, Windows 플랫폼 전체의 기반 구조와 깊게 연결되어 있다.
HWND는 포인터처럼 보여도 직접 접근하는 객체가 아니다
실제 헤더 파일을 보면 HWND 타입은 포인터처럼 정의되어 있다.
그래서 처음 보면 “객체 주소인가?”라는 착각이 생긴다.
하지만 중요한 것은 내부 구현이 아니라 사용 방식이다. Win32 개발자는 HWND를 직접 해석하지 않는다. 운영체제가 제공하는 API를 통해서만 사용한다.
즉 HWND는 “창 데이터 자체”가 아니라 “창에 접근하기 위한 키”에 가깝다.
이 관점이 잡히면 Win32 API 학습 속도가 훨씬 빨라진다. 이후 등장하는 메시지 루프, GDI, 컨트롤 시스템도 모두 같은 구조 위에서 동작하기 때문이다.