[시리즈 1편] 실무로 배우는 메시지 큐 - Windows Message Loop
들어가며
이 글은 "실무로 배우는 메시지 큐" 시리즈의 첫 번째 글입니다.
실무에서 발견한 문제를 해결하는 과정에서, IME 입력 문제와 해결 과정을 공유합니다.
메시지 큐는 RabbitMQ, Kafka 같은 네트워크 레벨만 있는 게 아닙니다. 우리가 매일 쓰는 Windows 애플리케이션도 메시지 큐 기반으로 동작합니다.
- 시리즈1 (이 글): 프로세스 내부의 메시지 큐 - Windows Message Loop의 이해와 활용
- 시리즈2: 네트워크 레벨의 메시지 큐 - RabbitMQ
문제 상황
MES 시스템 운영 중 고객으로부터 이런 문의가 반복적으로 들어왔습니다.
"설비코드 검색이 안 돼요. 분명히 SS0002 입력했는데..."
확인해보니 실제 입력된 값은 SS0002 (전각 문자)였습니다. 작업자들이 메모장, 엑셀, MES를 오가며 작업하다 보니 IME 상태가 전각 모드로 고정되어 발생한 문제였습니다.
반각: SS0002, ABC123 → 검색 가능
전각: SS0002, ABC123 → 검색 불가
처음엔 특정 메뉴에만 적용하려 했으나, 여러 화면에서 동일한 문제가 발생해 전체 시스템 레벨에서 해결하기로 했습니다.
시도한 방법들
| 방법 | 결과 | 문제점 |
| ------------------------- | ---- | ------------------------------------------ |
| TextChanged 이벤트 | ❌ | 무한 루프 (Text 변경 → 이벤트 발생 → 반복) |
| KeyPress 이벤트 | ❌ | IME 조합 중 문자 캐치 못함 |
| IMessageFilter (WM_CHAR) | ❌ | WM_IME_CHAR는 조합 완료 후에만 도착 |
| Form.Activated + IME 리셋 | △ | 메인폼만 동작, 팝업은 안 됨 |
| **Application.Idle** | ✅ | **모든 폼에서 동작, 입력 직후 변환** |
해결책: Application.Idle 이벤트
Windows 메시지 큐 아키텍처
Windows Forms는 메시지 큐 기반으로 동작합니다. 모든 사용자 입력(키보드, 마우스)은 메시지로 변환되어 큐에 쌓이고, Application.Run이 순차적으로 처리합니다.
┌─────────────────────────────────────────────────────┐
│ Windows Message Queue │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ WM_ │→ │ WM_ │→ │ WM_ │→ │ WM_ │ ... │
│ │ KEY │ │ IME │ │MOUSE │ │PAINT │ │
│ │ DOWN │ │ CHAR │ │ MOVE │ │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│Application.Run │ ← 메시지 루프 (Message Pump)
│ (Main Thread) │
└─────────────────┘
│
├─→ 큐에 메시지 있음 → 메시지 처리
│ (WM_IME_CHAR → TextBox에 'A' 입력)
│
└─→ 큐가 비어있음 → Application.Idle 발생 ⚡
(우리 코드 실행: 'A' → 'A' 변환)
핵심 아이디어
Application.Idle은 메시지 큐가 비어있을 때, 즉 모든 입력 처리가 완료된 직후 발생합니다.
// Application.Run 내부 동작 (의사 코드)
while (프로그램 실행 중)
{
if (메시지큐에 메시지 있음)
메시지 처리(); // WM_KEYDOWN → TextBox 입력
else
Application.Idle 발생(); // ← 여기서 변환!
}
타이밍 비교: 왜 Idle이 완벽한가?
타임라인: 전각 'A' 입력 시 각 이벤트 발생 시점
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[입력 시작] [IME 조합] [문자 확정] [화면 업데이트]
│ │ │ │
▼ ▼ ▼ ▼
─────●─────────────●─────────────●────────────────●──────→ 시간
❌ KeyPress (너무 빠름 - IME 조합 전이라 캐치 못함)
❌ IMessageFilter (이미 조합 완료 후)
❌ TextChanged (변환 시 다시 발생 → 무한루프)
✅ Application.Idle
(큐 비었음 = 입력 완료)
Application.Idle이 완벽한 이유:
- ✅ 입력이 완료된 후 호출 (TextBox에 이미 표시된 상태)
- ✅ 한 번만 실행 (무한루프 없음)
- ✅ 모든 폼에서 자동 동작
구현 코드
Program.cs
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// 전각→반각 자동 변환 등록
Application.Idle += FullWidthConverter.Application_Idle;
Application.Run(new MainForm());
}
FullWidthConverter.cs
public static class FullWidthConverter
{
public static void Application_Idle(object sender, EventArgs e)
{
try
{
Control focused = GetFocusedControl();
if (focused == null) return;
// 포커스된 컨트롤의 텍스트 변환
if (focused is TextBox tb)
ConvertText(tb, () => tb.Text, s => tb.Text = s,
() => tb.SelectionStart, v => tb.SelectionStart = v);
// TextEdit, ComboBoxEdit 등 동일 패턴
}
catch { }
}
private static string ToHalfWidth(string s)
{
if (string.IsNullOrEmpty(s)) return s;
char[] c = s.ToCharArray();
for (int i = 0; i < c.Length; i++)
{
// 전각 공백 → 반각
if (c[i] == '\u3000') c[i] = ' ';
// 전각 ASCII → 반각 (0xFEE0 차이)
else if (c[i] >= '\uFF01' && c[i] <= '\uFF5E')
c[i] = (char)(c[i] - 0xFEE0);
}
return new string(c);
}
}
전각↔반각 변환 원리
전각 'A' = U+FF21
반각 'A' = U+0041
차이값 = 0xFEE0 ← 이 값만 빼면 변환 완료
실행 흐름 (시퀀스 다이어그램)
사용자가 전각 'A' 입력 시
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────────┐
│ 사용자 │ │ Windows │ │ Message Queue│ │FullWidth │
│ │ │ OS │ │ │ │Converter │
└────┬────┘ └────┬─────┘ └──────┬──────┘ └──────┬───────┘
│ │ │ │
│ 'A' 키 입력 │ │ │
│──────────────────>│ │ │
│ │ │ │
│ │ WM_IME_CHAR 생성 │ │
│ │──────────────────────>│ │
│ │ │ │
│ │ 메시지 처리 시작 │ │
│ │<──────────────────────│ │
│ │ │ │
│ │ TextBox.Text = "A" │ │
│ │ (화면에 전각 표시) │ │
│ │ │ │
│ │ 큐가 비어있음! │ │
│ │ Application.Idle 발생│ │
│ │───────────────────────────────────────────────>│
│ │ │ │
│ │ │ 'A' → 'A' 변환 │
│ │ │ TextBox.Text = "A" │
│ │ │ │
│ 화면에 'A' 표시 (너무 빨라서 'A'는 못 봄) │
│<───────────────────────────────────────────────────────────────────│
│ │ │ │
소요 시간: 전체 과정이 약 10ms 이내에 완료되어 사용자는 전각 입력을 인지하지 못합니다.
붙여넣기(Ctrl+V)도 동일: WM_PASTE 메시지 처리 후 Idle 발생 → 변환
성능 영향
Q: Idle 이벤트가 너무 자주 호출되지 않나요?
실제 측정 결과 CPU 사용량 0.01% 미만입니다.
마치며
적용 결과
- ✅ 전각 문자 입력 문의 100% 해소
- ✅ 사용자 추가 조작 불필요 (자동 변환)
- ✅ 기존 코드 수정 없이
Program.cs한 줄 추가로 전체 적용
핵심 교훈
"타이밍이 중요하다"
TextChanged: 너무 빠름 (변경 중)KeyPress: 너무 빠름 (조합 전)Application.Idle: 딱 맞음 (입력 완료 직후)
Windows 메시지 루프를 이해하면 비슷한 UI 타이밍 문제를 해결할 때 유용합니다.
참고 자료
다음 글 예고
이번 글은 프로세스 내부 메시지 큐를 다뤘습니다.
다음 편에서는 네트워크 간 메시지 큐인 RabbitMQ를 다룹니다.