[WinAPI] WindowProc 메시지 처리 함수 완벽 가이드
🖥️ 시스템과 사용자가 주고받는 메시지 흐름, WindowProc로 완벽 이해하기
윈도우 프로그램을 개발하다 보면, 버튼 클릭이나 키보드 입력 같은 사용자의 동작뿐만 아니라 시스템 내부에서 발생하는 다양한 이벤트를 처리해야 하는 순간이 옵니다.
그 중심에 있는 핵심 함수가 바로 WindowProc입니다.
이 함수는 운영체제와 애플리케이션 사이에서 메시지를 받아 처리하는 관문 역할을 하며, 프로그램의 반응성과 기능 구현에 직접적인 영향을 미칩니다.
이 글에서는 WindowProc이 어떤 구조로 메시지를 받는지, 그리고 각각의 메시지를 어떻게 처리하면 좋은지 단계별로 살펴보겠습니다.
특히 초보 개발자들이 헷갈리기 쉬운 메시지 루프와 콜백 처리 방식까지 함께 설명하므로, 단순한 개념 이해를 넘어 실전에서 바로 적용할 수 있는 지식을 얻을 수 있을 것입니다.
지금부터 WindowProc의 동작 원리를 하나씩 풀어보겠습니다.
윈도우 프로그래밍의 기본은 이벤트 기반 구조입니다.
이벤트가 발생하면 시스템은 해당 애플리케이션의 메시지 큐에 정보를 전달하고, 애플리케이션은 이를 받아 적절히 처리합니다.
WindowProc 함수는 이 과정의 핵심 엔진으로, 각 메시지에 맞는 동작을 수행하게 됩니다.
따라서 이 함수를 정확히 이해하고 활용하는 것은 안정적이고 반응성 좋은 애플리케이션을 만드는 첫걸음입니다.
📋 목차
📌 WindowProc의 역할과 구조
WinAPI 기반 애플리케이션에서 WindowProc 함수는 운영체제로부터 전달되는 모든 메시지를 처리하는 중심 함수입니다.
이 함수는 콜백(callback) 형태로 정의되며, 프로그램이 실행되는 동안 키 입력, 마우스 클릭, 윈도우 크기 변경, 종료 요청 등 수많은 이벤트를 받아들이게 됩니다.
즉, 사용자의 행동이나 시스템 이벤트가 발생할 때마다 운영체제가 해당 메시지를 WindowProc으로 보내고, 개발자는 그 안에서 해당 메시지에 맞는 로직을 구현하게 됩니다.
WindowProc은 일반적으로 다음과 같은 시그니처로 정의됩니다.
LRESULT CALLBACK WindowProc(
HWND hwnd, // 메시지를 받은 윈도우의 핸들
UINT uMsg, // 메시지 식별자
WPARAM wParam, // 추가 메시지 정보 (정수형)
LPARAM lParam // 추가 메시지 정보 (포인터 또는 구조체)
);
여기서 hwnd는 메시지를 수신한 특정 윈도우를 가리키며, uMsg는 메시지의 종류를 나타내는 고유 값입니다.
🗂️ WindowProc의 핵심 처리 방식
일반적으로 WindowProc 내부에서는 switch-case 문을 사용하여 uMsg 값에 따라 분기 처리를 합니다.
예를 들어, WM_PAINT 메시지는 윈도우를 다시 그려야 할 때 발생하며, WM_DESTROY 메시지는 윈도우가 닫힐 때 호출되어 프로그램 종료 절차를 진행합니다.
switch (uMsg) {
case WM_PAINT:
// 화면 갱신 처리
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
💡 TIP: 모든 메시지를 직접 처리할 필요는 없습니다.
처리하지 않는 메시지는 DefWindowProc 함수를 호출하여 기본 동작을 수행하게 해야 합니다.
⚙️ 메시지 루프와 처리 흐름
WinAPI 애플리케이션은 이벤트 기반으로 동작하며, 실행 스레드의 메시지 루프가 사용자·시스템 이벤트를 큐에서 꺼내 WindowProc으로 전달하는 흐름을 담당합니다.
핵심 개념은 간단합니다.
사용자 또는 시스템으로부터 전달되는 메시지를 WindowProc 함수에서 받아 처리하는 구조입니다.
이 구조 덕분에 버튼 클릭, 키보드 입력, 창 이동·리사이즈 같은 다양한 상황을 일관된 방식으로 다룰 수 있습니다.
🔄 메시지 루프의 표준 패턴
가장 널리 쓰이는 루프는 GetMessage → TranslateMessage → DispatchMessage 순서를 따릅니다.
GetMessage는 큐에서 메시지를 꺼낼 때까지 대기하고, TranslateMessage는 키보드 관련 보조 메시지를 생성하며, DispatchMessage는 해당 메시지를 적절한 윈도우의 WindowProc에 보내 실제 처리를 수행하도록 합니다.
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow) {
// ... 윈도우 클래스 등록 및 생성
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) { // 메시지가 오기까지 대기
TranslateMessage(&msg); // 키보드 변환 (WM_CHAR 등)
DispatchMessage(&msg); // 해당 윈도우의 WindowProc 호출
}
return static_cast<int>(msg.wParam);
}
⌛ GetMessage vs PeekMessage
GetMessage는 메시지가 없으면 블로킹되어 CPU를 낭비하지 않습니다.
반면 PeekMessage는 메시지가 있으면 가져오고 없으면 즉시 반환하므로, 게임 루프나 타이머 기반 애니메이션처럼 비동기 업데이트가 필요한 상황에 적합합니다.
단, 바쁜 루프를 구성할 땐 Sleep이나 타이머를 적절히 사용해 과도한 CPU 사용을 피해야 합니다.
MSG msg = {};
bool running = true;
while (running) {
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) { running = false; break; }
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 메시지가 없는 틱에서 업데이트/렌더링 등 수행
// ... Update(); Render();
// Sleep(1); // 필요 시 과열 방지
}
🧭 DispatchMessage 이후의 흐름
DispatchMessage가 호출되면 운영체제는 메시지 대상 윈도우를 식별하고, 해당 윈도우의 WindowProc을 호출합니다.
여기서 메시지 식별자(uMsg)에 따라 분기 처리되고, 처리하지 않는 경우 DefWindowProc으로 위임해 기본 동작을 유지합니다.
이 전체 여정이 바로 “큐 → 루프 → Dispatch → WindowProc → 처리/위임”의 표준 사이클입니다.
- 📥GetMessage는 메시지가 없을 때 대기하여 CPU를 절약합니다.
- 🔎PeekMessage는 비블로킹으로, 실시간 업데이트 루프에 적합합니다.
- 🎯DispatchMessage가 WindowProc을 호출해 실제 메시지를 처리합니다.
- 🧱처리하지 않는 메시지는 DefWindowProc에 위임합니다.
⚠️ 주의: 메시지 루프 내부에서 장시간 블로킹 작업(파일 I/O, 네트워크 호출 등)을 수행하면 UI가 멈춘 것처럼 보일 수 있습니다.
필요 시 백그라운드 스레드나 비동기 I/O를 활용하고, 작업 완료 후 UI 갱신은 메시지(예: PostMessage)를 통해 메인 스레드에 요청하세요.
🛠️ 주요 메시지 유형과 처리 방법
WindowProc에서는 수백 가지 메시지를 만날 수 있지만, 실제로 자주 다루는 범주는 몇 가지로 압축됩니다.
생성과 종료, 입력(키보드·마우스), 그리기(페인팅), 크기 변경/레이아웃, 명령 전달(버튼 클릭·메뉴), 타이머/비동기 알림이 대표적입니다.
각 메시지는 uMsg로 식별되며, wParam과 lParam에 세부 정보가 담겨 전달됩니다.
처리하지 않는 메시지는 기본 동작을 유지하기 위해 DefWindowProc으로 넘겨야 합니다.
🏗️ 생성/종료: WM_CREATE, WM_DESTROY
윈도우가 만들어질 때 WM_CREATE가 먼저 도착합니다.
초기 컨트롤 생성, 리소스 로드, 타이머 시작 등을 배치하기 좋은 시점입니다.
윈도우가 닫힐 때 WM_DESTROY가 발생하며, 일반적으로 PostQuitMessage(0)를 호출해 메시지 루프 종료를 알립니다.
⌨️🖱️ 입력: WM_KEYDOWN, WM_CHAR, WM_LBUTTONDOWN 등
키가 눌릴 때는 WM_KEYDOWN이, 글자 입력 시에는 WM_CHAR가 도착합니다.
마우스 왼쪽 버튼 클릭은 WM_LBUTTONDOWN이고, 좌표는 lParam의 하위/상위 워드에 각각 X/Y 픽셀로 들어옵니다.
필요 시 GET_X_LPARAM, GET_Y_LPARAM 매크로를 사용하면 안전합니다.
🎨 그리기/레이아웃: WM_PAINT, WM_SIZE
윈도우를 다시 그려야 할 때는 WM_PAINT가 발생합니다.
이 메시지 내부에서는 BeginPaint/EndPaint를 사용해 페인팅 컨텍스트를 얻고, 필요한 그리기를 수행합니다.
크기 변경 시 전달되는 WM_SIZE에서는 새로운 폭/높이가 lParam에 담겨 오므로 레이아웃 업데이트에 활용합니다.
🧩 명령 전달/알림: WM_COMMAND, WM_NOTIFY
버튼 클릭, 메뉴 선택 등은 WM_COMMAND로 수신합니다.
메뉴/액셀러레이터/컨트롤에 따라 HIWORD(wParam)와 LOWORD(wParam)의 의미가 달라지므로 분기 처리에 주의합니다.
리스트뷰/트리뷰 같은 공용 컨트롤의 상세 알림은 WM_NOTIFY로 구조체 포인터가 lParam을 통해 전달됩니다.
⏱️ 타이머/비동기: WM_TIMER, PostMessage
주기 작업은 SetTimer로 타이머를 등록하고, 콜백 대신 WM_TIMER를 WindowProc에서 처리합니다.
백그라운드 작업 완료 알림은 UI 스레드로 직접 호출하지 말고 PostMessage로 안전하게 전달하는 것이 좋습니다.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE: {
SetTimer(hwnd, 1, 1000, nullptr); // 1초 주기
return 0;
}
case WM_COMMAND: {
const int id = LOWORD(wParam); // 컨트롤/메뉴 ID
const int code = HIWORD(wParam); // 알림 코드
HWND hFrom = (HWND)lParam; // 보낸 컨트롤 핸들(컨트롤일 때)
// 예: 버튼(IDOK) 클릭 처리
if (id == IDOK && code == BN_CLICKED) { /* ... */ }
return 0;
}
case WM_LBUTTONDOWN: {
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
// 좌표(x, y) 사용
return 0;
}
case WM_SIZE: {
int cx = LOWORD(lParam), cy = HIWORD(lParam);
// 레이아웃 업데이트
return 0;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 그리기 코드
EndPaint(hwnd, &ps);
return 0;
}
case WM_TIMER: {
if (wParam == 1) { /* 주기 작업 */ }
return 0;
}
case WM_DESTROY:
KillTimer(hwnd, 1);
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
| 메시지 | 주요 목적 | wParam / lParam |
|---|---|---|
| WM_CREATE | 초기화, 컨트롤 생성 | lParam에 CREATESTRUCT* |
| WM_COMMAND | 버튼/메뉴/컨트롤 명령 | LOWORD=id, HIWORD=코드, lParam=HWND |
| WM_LBUTTONDOWN | 마우스 좌클릭 | lParam=좌표(X,Y) |
| WM_PAINT | 화면 다시 그리기 | BeginPaint/EndPaint 필수 |
| WM_SIZE | 크기 변경 대응 | LOWORD=폭, HIWORD=높이 |
| WM_TIMER | 주기 작업 트리거 | wParam=타이머 ID |
💡 TIP: 좌표가 음수가 될 수 있는 트래킹 상황 등에서는 GET_X/Y_LPARAM 매크로를 사용하면 사인 확장을 올바르게 처리할 수 있습니다.
⚠️ 주의: WM_PAINT 외부에서 임의로 HDC를 얻어 장시간 그리면 깜빡임과 성능 저하가 생길 수 있습니다.
필요 시 더블 버퍼링이나 InvalidateRect + UpdateWindow 조합으로 그리기 타이밍을 제어하세요.
💡 커스텀 메시지 정의와 활용
응용 프로그램만의 이벤트를 만들고 싶을 때는 사용자 정의 메시지를 활용합니다.
윈도우 운영체제는 기본적으로 수백 개의 표준 메시지를 제공하지만, 모듈 간 통신이나 비동기 작업 완료 알림 같은 시나리오에서는 앱만의 고유 메시지가 필요합니다.
핵심은 간단합니다.
사용자 또는 시스템으로부터 전달되는 메시지를 WindowProc 함수에서 받아 처리하는 구조에 우리만의 메시지를 더하는 것입니다.
이를 위해 WM_USER 영역, WM_APP 영역, 그리고 RegisterWindowMessage 방식이 제공됩니다.
🏷️ WM_USER vs WM_APP vs RegisterWindowMessage
컨트롤 라이브러리나 공용 컨트롤이 내부적으로 쓰는 범위가 WM_USER(0x0400+)이며, 애플리케이션 전용으로 충돌 위험이 적은 권장 범위는 WM_APP(0x8000+)입니다.
여러 프로세스가 같은 문자열 키로 유일한 메시지 번호를 공유해야 한다면 RegisterWindowMessage를 활용합니다.
문자열 기반 해시로 시스템 전체에서 유일한 식별자를 부여하므로 DLL 경계나 다른 프로세스와의 브로드캐스트에도 안전합니다.
| 구분 | 특징 | 권장 사용 사례 |
|---|---|---|
| WM_USER + N | 컨트롤/라이브러리 전용 범위. 다른 컨트롤과 충돌 가능성. |
커스텀 컨트롤 내부용. |
| WM_APP + N | 애플리케이션 전용. 프로세스 내 충돌 위험 낮음. |
앱 내부 창 간 통신. |
| RegisterWindowMessage | 문자열로 시스템 전역 유일 ID 부여. | 다른 프로세스와의 브로드캐스트. |
🧪 정의와 전송, 그리고 WindowProc에서의 처리
사용자 정의 메시지는 매크로로 ID를 정의하고, PostMessage 또는 SendMessage로 전송합니다.
UI 스레드 외부에서 결과를 통지할 땐 비동기인 PostMessage가 안전하며, 동기 호출이 필요할 땐 SendMessage를 사용하되 교착상태를 주의합니다.
메시지 페이로드는 wParam과 lParam에 담아 전달합니다.
구조체 포인터를 보낼 때는 수명과 소유권을 명확히 해 누수나 UAF를 방지하세요.
// 1) 애플리케이션 전용 메시지
#define WM_APP_TASK_DONE (WM_APP + 1)
// 2) 시스템 전역 유일 메시지 (프로세스 간 브로드캐스트)
UINT WM_TASK_PROGRESS = RegisterWindowMessage(TEXT("MyCompany.MyApp.TaskProgress"));
// 작업 스레드에서 메인 윈도우로 비동기 통지
void WorkerNotify(HWND hMain, int percent) {
// wParam에 진행률, lParam은 예약
PostMessage(hMain, WM_TASK_PROGRESS, (WPARAM)percent, 0);
}
// WindowProc에서 처리
LRESULT CALLBACK WindowProc(HWND h, UINT u, WPARAM w, LPARAM l) {
switch (u) {
case WM_APP_TASK_DONE:
// 작업 완료 처리
// 예: 버튼 재활성화, 상태 표시 갱신
return 0;
default:
// RegisterWindowMessage로 받은 값은 컴파일 타임 상수가 아니므로
// switch 문과 별개로 if (u == WM_TASK_PROGRESS) 형태로 비교
if (u == WM_TASK_PROGRESS) {
int pct = (int)w; // 진행률 0~100
// 진행바 업데이트
return 0;
}
return DefWindowProc(h, u, w, l);
}
}
📮 Message-only 윈도우와 브로드캐스트
UI를 갖지 않는 통신 전용 창이 필요하다면 Message-only Window(HWND_MESSAGE)를 생성해 내부 모듈 간 메시지 허브로 사용할 수 있습니다.
다중 프로세스에 알림을 보내려면 BroadcastSystemMessage 또는 SendMessageTimeout과 함께 RegisterWindowMessage 값을 이용해 충돌을 피합니다.
- 🧩컨트롤 내부는 WM_USER, 앱 전용은 WM_APP 권장.
- 🔒RegisterWindowMessage는 프로세스 간 고유성 보장.
- 📤작업 스레드 → UI 통지는 PostMessage로 비동기 처리.
- 🧠포인터 전달 시 수명·소유권을 명확히 정의.
💎 핵심 포인트:
메시지 ID는 팀 규칙으로 문서화하고, 범위별 예약 및 충돌 방지 원칙을 명확히 하세요.
브로드캐스트가 필요 없다면 간단한 WM_APP + N이 유지보수에 가장 유리합니다.
⚠️ 주의: SendMessage로 스레드 간 동기 호출을 남발하면 교착상태가 발생할 수 있습니다.
필요 시 SendMessageTimeout을 사용하거나 설계를 PostMessage 중심으로 재구성하세요.
또한 다른 프로세스에 포인터를 전달하지 마세요.
프로세스 경계를 넘을 땐 공유 메모리나 파일 매핑 등 안전한 IPC를 사용해야 합니다.
🔍 디버깅과 성능 최적화 팁
WinAPI 앱에서 성능 저하나 UI 멈춤 현상은 대부분 메시지 처리 경로에서 발생합니다.
핵심은 사용자 또는 시스템으로부터 전달되는 메시지를 WindowProc 함수에서 받아 처리하는 구조를 이해하고, 불필요한 작업을 최소화하는 것입니다.
이 섹션에서는 메시지 흐름을 가시화하는 로깅 기법, 그리기/입력 처리의 병목 제거, 스레드 간 통신 안정화, 디버깅 도구 활용법을 정리합니다.
각 팁은 실제 프로젝트에 바로 적용할 수 있도록 코드와 체크리스트 중심으로 구성했습니다.
🧾 메시지 트레이싱으로 흐름 가시화
문제가 발생한 시점의 정확한 메시지 순서를 알면 원인을 빠르게 좁힐 수 있습니다.
간단한 래퍼를 만들어 uMsg와 인자를 출력 로그로 남기세요.
개발 빌드에서만 활성화하고, 릴리스 빌드에선 제거해 오버헤드를 없애는 것이 좋습니다.
// 디버그 전용 로깅 매크로
#ifndef NDEBUG
#define DBG(msg, ...) do { \
wchar_t _buf[256]; \
_snwprintf_s(_buf, _TRUNCATE, L"[DBG] " msg L"\n", __VA_ARGS__); \
OutputDebugStringW(_buf); \
} while(0)
#else
#define DBG(msg, ...) do {} while(0)
#endif
// 메시지 이름 헬퍼(일부 샘플)
const wchar_t* MsgName(UINT u) {
switch (u) {
case WM_CREATE: return L"WM_CREATE";
case WM_SIZE: return L"WM_SIZE";
case WM_PAINT: return L"WM_PAINT";
case WM_COMMAND:return L"WM_COMMAND";
default: return L"UNKNOWN";
}
}
LRESULT CALLBACK WindowProc(HWND h, UINT u, WPARAM w, LPARAM l) {
DBG(L"%s w=%p l=%p", MsgName(u), (void*)w, (void*)l);
// ... 실제 처리
return DefWindowProc(h, u, w, l);
}
🎨 페인팅 최적화와 깜빡임 감소
그리기는 비용이 큽니다.
WM_PAINT 내부에서만 그리기를 수행하고, BeginPaint/EndPaint 범위를 최소화하세요.
변경된 영역만 무효화하는 InvalidateRect와 조건부 UpdateWindow를 활용하면 과도한 리페인트를 줄일 수 있습니다.
더블 버퍼링(메모리 DC → BitBlt)으로 깜빡임을 낮추는 것도 효과적입니다.
// 더블 버퍼링 예시
case WM_PAINT: {
PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps);
RECT rc; GetClientRect(hwnd, &rc);
HDC mem = CreateCompatibleDC(hdc);
HBITMAP bmp = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HGDIOBJ old = SelectObject(mem, bmp);
// 메모리 DC에 모든 그리기 수행
// FillRect, TextOut, GDI+ 등...
BitBlt(hdc, 0, 0, rc.right, rc.bottom, mem, 0, 0, SRCCOPY);
SelectObject(mem, old);
DeleteObject(bmp); DeleteDC(mem);
EndPaint(hwnd, &ps);
return 0;
}
⚡ 입력/명령 처리의 지연 제거
버튼 클릭이나 키 입력을 처리하는 WM_COMMAND, WM_KEYDOWN 등에서 장시간 작업을 수행하면 메시지 큐가 막혀 UI 체감 속도가 떨어집니다.
무거운 연산은 작업 스레드로 넘기고, 완료 시 PostMessage나 WM_APP + N 사용자 정의 메시지로 UI를 갱신하세요.
- 🧭핵심 처리만 WindowProc에 두고, 나머지는 함수로 분리합니다.
- 🧵긴 작업은 워커 스레드에서 처리하고 UI 통지는 PostMessage로 비동기화합니다.
- 🖌️WM_PAINT 밖에서의 무분별한 그리기를 피하고 더블 버퍼링을 도입합니다.
- 🧪개발 빌드에서 OutputDebugString 기반 메시지 로깅을 켜고, 릴리스에서는 끕니다.
| 도구/기법 | 목적 | 활용 포인트 |
|---|---|---|
| OutputDebugString | 런타임 로깅 | 메시지/인자 기록으로 재현 어려운 이슈 추적 |
| InvalidateRect/UpdateWindow | 그리기 최소화 | 부분 무효화로 리페인트 범위 축소 |
| PostMessage | 비동기 UI 갱신 | 워커 완료 알림, 교착 회피 |
| 더블 버퍼링 | 깜빡임 감소 | 메모리 DC에서 렌더 후 화면 전송 |
💎 핵심 포인트:
메시지 처리 경로에서 “오래 걸리는 작업”을 제거하는 것이 최적화의 출발점입니다.
측정 없이 최적화하지 말고, 로깅으로 병목을 확인한 뒤 필요한 지점만 바꾸세요.
⚠️ 주의: SendMessage를 스레드 간 동기 호출로 남발하면 교착 상태에 빠질 수 있습니다.
타임아웃이 필요한 경우 SendMessageTimeout을 고려하고, 가능하면 PostMessage로 대체하세요.
또한 디버그 로깅은 릴리스에서 반드시 비활성화하거나 수준을 낮추어 성능 영향을 줄이세요.
❓ 자주 묻는 질문 (FAQ)
WindowProc과 메시지 루프(WinMain)의 역할은 어떻게 다른가요?
즉, 루프는 배달, WindowProc은 조리로 비유할 수 있습니다.
모든 메시지를 직접 처리해야 하나요, 아니면 DefWindowProc을 써도 되나요?
그래야 시스템 기본 동작(포커스 이동, 시스템 명령 등)이 올바르게 유지됩니다.
WM_PAINT는 왜 BeginPaint/EndPaint 안에서만 그려야 하나요?
이 범위를 벗어나 임의로 그리면 깜빡임, 오작동, 추가 리페인트 유발 등 부작용이 생깁니다.
SendMessage와 PostMessage 중 어떤 것을 언제 써야 하나요?
PostMessage는 비동기 전달로 UI 프리즈와 교착 위험을 줄일 수 있어, 워커 스레드에서 UI 갱신을 알릴 때 권장됩니다.
PeekMessage 루프를 쓸 때 CPU가 높게 나오는 이유와 대처법은?
틱 사이에 Sleep(1)이나 타이머/프레임 동기화를 넣어 과열을 방지하세요.
가능하다면 GetMessage 기반으로 전환하는 것도 방법입니다.
사용자 정의 메시지는 WM_USER와 WM_APP 중 어디에 정의하나요?
프로세스 간 고유성이 필요하면 RegisterWindowMessage를 사용하세요.
마우스 좌표는 lParam에서 어떻게 꺼내나요?
부호 확장 문제를 피하려면 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) 매크로를 사용하세요.
WindowProc에서 오래 걸리는 작업은 어떻게 처리하나요?
이렇게 하면 메시지 큐가 막히지 않아 UI가 부드럽게 유지됩니다.
🧭 WindowProc 메시지 처리 핵심 정리
WinAPI 애플리케이션의 중심은 WindowProc으로, 사용자 또는 시스템으로부터 전달되는 메시지를 WindowProc 함수에서 받아 처리하는 구조입니다.
메시지 루프는 GetMessage/TranslateMessage/DispatchMessage 순으로 큐의 이벤트를 운반하고, WindowProc은 uMsg에 따라 분기하여 실제 로직을 실행합니다.
처리하지 않는 메시지는 DefWindowProc에 위임해 기본 동작을 보장해야 합니다.
자주 사용하는 메시지는 WM_CREATE·WM_DESTROY(생명주기), WM_PAINT·WM_SIZE(그리기/레이아웃), WM_COMMAND·WM_NOTIFY(명령/알림), WM_KEYDOWN·WM_CHAR·WM_LBUTTONDOWN(입력), WM_TIMER(주기 작업)입니다.
게임/실시간 루프처럼 틱 처리가 필요한 경우 PeekMessage를 쓰되 과열 방지를 위한 Sleep/타이머를 병행합니다.
무거운 작업은 워커 스레드로 분리하고, UI 갱신은 PostMessage나 WM_APP+N으로 안전하게 통지합니다.
앱 고유 이벤트가 필요하면 WM_APP 범위를 우선적으로 활용하고, 프로세스 간 고유성이 필요할 때 RegisterWindowMessage를 사용합니다.
그리기 최적화는 WM_PAINT 내부의 최소 범위 렌더링과 더블 버퍼링이 핵심입니다.
문제 분석 시에는 메시지 트레이싱으로 흐름을 가시화해 병목을 정확히 찾는 습관이 안정성과 성능 모두에 도움이 됩니다.
🏷️ 관련 태그 : WinAPI, WindowProc, 메시지루프, GetMessage, PeekMessage, DefWindowProc, WM_PAINT, WM_COMMAND, 사용자정의메시지, Windows프로그래밍