◎ Part 4: WindowProc + WinMain (메시지 처리 & 진입점)
| // ------------ WindowProc sub ---------- WNDPROC g_OldEditProc = NULL; // hInputEdit, hOutputEdit용 (Ctrl+A만) WNDPROC g_OldTextAProc = NULL; // hTextA용 (Ctrl+A + TAB → TextB 이동) extern HWND hTextA; // Text A 핸들 extern HWND hTextB; // Text B 핸들 // 일반 Edit용: Ctrl+A만 처리 (InputEdit, OutputEdit에 적용) LRESULT CALLBACK EditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_KEYDOWN) { if (wParam == 'A' && (GetKeyState(VK_CONTROL) & 0x8000)) { SendMessage(hwnd, EM_SETSEL, 0, -1); return 0; } } return CallWindowProc(g_OldEditProc, hwnd, msg, wParam, lParam); } // TextA 전용: Ctrl+A + TAB → TextB로 이동 LRESULT CALLBACK TextAProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_KEYDOWN) { // Ctrl+A 전체 선택 if (wParam == 'A' && (GetKeyState(VK_CONTROL) & 0x8000)) { SendMessage(hwnd, EM_SETSEL, 0, -1); return 0; } // TAB 키 (Shift 안 눌린 경우에만 TextB로 이동) if (wParam == VK_TAB) { if (!(GetKeyState(VK_SHIFT) & 0x8000)) // Shift+TAB은 기본 동작 허용 { SetFocus(hTextB); return 0; } } } return CallWindowProc(g_OldTextAProc, hwnd, msg, wParam, lParam); } // ------------ WindowProc ------------ LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: CreateControls(hwnd); // 기존 Input Edit, Output Edit: Ctrl+A만 g_OldEditProc = (WNDPROC)SetWindowLongPtr( hInputEdit, GWLP_WNDPROC, (LONG_PTR)EditProc ); SetWindowLongPtr( // 반환값 저장 안 해도 되지만 일관성 위해 hOutputEdit, GWLP_WNDPROC, (LONG_PTR)EditProc ); // 별도의 Text A: Ctrl+A + TAB → Text B 이동 g_OldTextAProc = (WNDPROC)SetWindowLongPtr( hTextA, GWLP_WNDPROC, (LONG_PTR)TextAProc ); // Text B는 서브클래싱 안 해도 됨 (필요하면 Ctrl+A만 넣어도 OK) return 0; case WM_COMMAND: { int btnId = LOWORD(wParam); static char input[MAX_BUFFER] = { 0 }; static char output[MAX_BUFFER] = { 0 }; GetWindowTextA(hInputEdit, input, MAX_BUFFER - 1); output[0] = '\0'; // 이전 결과 제거 bool converted = true; switch (btnId) { case IDC_TEXT_TO_HEX: textToHex(input, output, MAX_BUFFER); break; case IDC_HEX_TO_TEXT: hexToText(input, output, MAX_BUFFER); break; case IDC_DEC_TO_HEX: decToHex(input, output, MAX_BUFFER); break; case IDC_HEX_TO_DEC: hexToDec(input, output, MAX_BUFFER); break; case IDC_TEXT_TO_DEC: textToDec(input, output, MAX_BUFFER); break; case IDC_DEC_TO_TEXT: decToText(input, output, MAX_BUFFER); break; case IDC_DEC_TO_OCTAL: decToOctal(input, output, MAX_BUFFER); break; case IDC_OCTAL_TO_DEC: octalToDec(input, output, MAX_BUFFER); break; case IDC_TEXT_TO_UTF7: textToUtf7(input, output, MAX_BUFFER); break; case IDC_UTF7_TO_TEXT: utf7ToText(input, output, MAX_BUFFER); break; case IDC_HEX_TO_UCS2: hexToUcs2(input, output, MAX_BUFFER); break; case IDC_UCS2_TO_HEX: ucs2ToHex(input, output, MAX_BUFFER); break; case IDC_TEXT_TO_BINARY: textToBinary(input, output, MAX_BUFFER); break; case IDC_BINARY_TO_TEXT: binaryToText(input, output, MAX_BUFFER); break; case IDC_ESCAPE: escapeString(input, output, MAX_BUFFER); break; case IDC_UNESCAPE: unescapeString(input, output, MAX_BUFFER); break; case IDC_ENCODE_HTML: encodeHTML(input, output, MAX_BUFFER); break; case IDC_DECODE_HTML: decodeHTML(input, output, MAX_BUFFER); break; case IDC_TEXT_TO_BASE64: encodeBase64(input, output, MAX_BUFFER); break; case IDC_BASE64_TO_TEXT: decodeBase64(input, output, MAX_BUFFER); break; case IDC_HEX_TO_BASE64: hexToBase64(input, output, MAX_BUFFER); break; case IDC_BASE64_TO_HEX: base64ToHex(input, output, MAX_BUFFER); break; case IDC_IP_TO_DEC: ipToDec(input, output, MAX_BUFFER); break; case IDC_DEC_TO_IP: decToIp(input, output, MAX_BUFFER); break; case IDC_IP_TO_HEX: ipToHex(input, output, MAX_BUFFER); break; case IDC_HEX_TO_IP: hexToIp(input, output, MAX_BUFFER); break; case IDC_TEXT_TO_URL: textToUrl(input, output, MAX_BUFFER); break; case IDC_URL_TO_TEXT: urlToText(input, output, MAX_BUFFER); break; case IDC_REPLACE_GO: { static char findText[32] = { 0 }; static char replaceText[32] = { 0 }; GetWindowTextA(hTextA, findText, sizeof(findText) - 1); GetWindowTextA(hTextB, replaceText, sizeof(replaceText) - 1); replaceTextInInput(hInputEdit, findText, replaceText); converted = false; break; } case IDC_COPY_OUTPUT: { static char buf[MAX_BUFFER] = { 0 }; GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1); copyToClipboard(buf); converted = false; break; } case IDC_COPY_TO_INPUT: { static char buf[MAX_BUFFER] = { 0 }; GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1); SetWindowTextA(hInputEdit, buf); converted = false; break; } case IDC_CLEAR_ALL: SetWindowTextA(hTextA, ""); SetWindowTextA(hTextB, ""); SetWindowTextA(hInputEdit, ""); SetWindowTextA(hOutputEdit, ""); converted = false; break; case IDC_FILE_SELECT: openFileSelectDialog(hwnd, hOutputEdit); converted = false; // ← 중요: 이미 함수 내부에서 출력했으므로 break; default: converted = false; break; } // 핵심: 변환 버튼이면 결과 출력 if (converted) SetWindowTextA(hOutputEdit, output); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } // ------------ WinMain ------------ int WINAPI WinMain( _In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrev, _In_ LPSTR lpCmd, _In_ int nCmdShow) { const wchar_t CLASS_NAME[] = L"ConvToolImproved"; WNDCLASSW wc = { 0 }; wc.lpfnWndProc = WindowProc; wc.hInstance = hInst; wc.lpszClassName = CLASS_NAME; wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.hCursor = LoadCursorW(NULL, IDC_ARROW); if (!RegisterClassW(&wc)) return 0; HWND hwnd = CreateWindowExW( 0, CLASS_NAME, L"Data Conversion Tool", WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME, CW_USEDEFAULT, CW_USEDEFAULT, 680, 570, NULL, NULL, hInst, NULL); if (!hwnd) return 0; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg = { 0 }; while (GetMessageW(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessageW(&msg); } return (int)msg.wParam; } |
- WindowProc: 메시지 처리
| LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: CreateControls(hwnd); break; |
- 윈도우 생성될 때: CreateControls()를 호출해서 모든 UI 생성.
| case WM_COMMAND: { int btnId = LOWORD(wParam); char* input = (char*)malloc(MAX_BUFFER); char* output = (char*)malloc(MAX_BUFFER); if (!input || !output) { if (input) free(input); if (output) free(output); break; } ZeroMemory(input, MAX_BUFFER); ZeroMemory(output, MAX_BUFFER); |
- 사용자가 버튼을 클릭할 때 발생.
- btnId : 클릭된 버튼의 ID (201~228 중 하나).
- 동적 메모리 할당 : 8KB짜리 입력/출력 버퍼.
- ZeroMemory() : 메모리를 0으로 초기화 (쓰레기값 제거).
| GetWindowTextA(hInputEdit, input, MAX_BUFFER - 1); |
- 입력 텍스트박스에서 사용자가 입력한 텍스트를 읽기.
| switch (btnId) { case IDC_TEXT_TO_HEX: textToHex(input, output, MAX_BUFFER); break; case IDC_HEX_TO_TEXT: hexToText(input, output, MAX_BUFFER); break; // ... 등등 case IDC_TEXT_TO_BASE64: encodeBase64(input, output, MAX_BUFFER); break; case IDC_BASE64_TO_TEXT: decodeBase64(input, output, MAX_BUFFER); break; // ... } |
- 클릭된 버튼에 따라 해당 변환 함수 호출.
- 예: IDC_TEXT_TO_HEX면 textToHex() 함수 실행.
| case IDC_COPY_OUTPUT: { char* buf = (char*)malloc(MAX_BUFFER); if (buf) { GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1); copyToClipboard(buf); free(buf); } free(input); free(output); return 0; } |
- "Copy Output to Clipboard" 버튼
>> 출력 텍스트박스의 내용을 읽고
>> copyToClipboard()로 클립보드에 복사
>> 메모리 해제 후 바로 반환 (SetWindowTextA 호출 안 함).
| case IDC_COPY_TO_INPUT: { char* buf = (char*)malloc(MAX_BUFFER); if (buf) { GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1); SetWindowTextA(hInputEdit, buf); free(buf); } free(input); free(output); return 0; } |
- "Copy Output to Input" 버튼
>> 출력 내용을 입력 텍스트박스에 붙여넣기.
>> 메모리 해제.
| case IDC_CLEAR_ALL: SetWindowTextA(hInputEdit, ""); SetWindowTextA(hOutputEdit, ""); free(input); free(output); return 0; |
- "Clear All" 버튼: 입력/출력 박스 모두 비우기.
| SetWindowTextA(hOutputEdit, output); free(input); free(output); break; } |
- 변환 함수 호출 후 결과를 출력 텍스트박스에 표시.
- 메모리 해제 (malloc 되었으니 반드시 필요).
| case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } |
- 윈도우가 닫힐 때 프로그램 종료.
- WinMain: 진입점
| int WINAPI WinMain( _In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrev, _In_ LPSTR lpCmd, _In_ int nCmdShow) { const wchar_t CLASS_NAME[] = L"ConvToolImproved"; |
- 프로그램의 시작점.
- hInst : 현재 프로그램의 인스턴스 핸들.
- nCmdShow : 윈도우를 어떻게 보여줄지 (최소화/정상/최대화).
- CLASS_NAME : 윈도우 클래스 이름 (등록할 클래스 이름).
| WNDCLASSW wc = { 0 }; wc.lpfnWndProc = WindowProc; wc.hInstance = hInst; wc.lpszClassName = CLASS_NAME; wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.hCursor = LoadCursorW(NULL, IDC_ARROW); if (!RegisterClassW(&wc)) return 0; |
- 윈도우 클래스 정의:
>> lpfnWndProc : 메시지 처리 함수 (WindowProc).
>> hInstance : 인스턴스.
>> lpszClassName : 클래스 이름.
>> hbrBackground : 배경색 (버튼 면 색상).
>> hCursor : 마우스 커서 (기본 화살표).
>> RegisterClassW() : 이 클래스를 윈도우 시스템에 등록.
| HWND hwnd = CreateWindowExW( 0, CLASS_NAME, L"Data Conversion Tool", WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME, CW_USEDEFAULT, CW_USEDEFAULT, 680, 480, NULL, NULL, hInst, NULL); if (!hwnd) return 0; |
- 실제 윈도우 생성
>> CLASS_NAME : 위에서 등록한 클래스 사용
>> L"Data Conversion Tool" : 타이틀 바에 표시될 제목
>> WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME :
>>> 기본 윈도우 스타일에서
>>> 최대화 버튼 제거 (~WS_MAXIMIZEBOX)
>>> 크기 조절 불가능 (~WS_THICKFRAME)
>> 680, 480 : 초기 윈도우 크기 (680×480).
| ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); |
- 윈도우 표시 (ShowWindow).
- 윈도우 갱신 (UpdateWindow) - 화면에 즉시 그리기.
| MSG msg = { 0 }; while (GetMessageW(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessageW(&msg); } return (int)msg.wParam; |
- 메시지 루프 (프로그램의 핵심):
>> GetMessageW() : 윈도우 메시지 대기 (사용자 클릭, 키 입력 등).
>> TranslateMessage() : 키 입력을 문자로 변환.
>> DispatchMessageW() : 메시지를 WindowProc로 전달.
>> 루프는 WM_QUIT 메시지를 받을 때까지 계속 반복.
◎ 실행 흐름 요약
| 1. WinMain 시작 ↓ 2. 윈도우 클래스 등록 (WNDCLASSW) ↓ 3. 윈도우 생성 (CreateWindowExW) ↓ 4. 윈도우 표시 (ShowWindow) ↓ 5. 메시지 루프 시작 (while GetMessageW) ├─ 사용자 입력 대기 ├─ WindowProc에서 메시지 처리 │ ├─ WM_CREATE : CreateControls() 호출 (UI 생성) │ ├─ WM_COMMAND : 버튼 클릭 감지 │ │ ├─ 변환 함수 호출 (textToHex, hexToText 등) │ │ └─ 결과를 출력 텍스트박스에 표시 │ └─ WM_DESTROY : PostQuitMessage(0) │ └─ 루프 종료 (WM_QUIT 받음) ↓ 6. 프로그램 종료 |
'IT > Coding' 카테고리의 다른 글
| [C언어] Data Conversion Tool #03 (0) | 2025.12.14 |
|---|---|
| [C언어] Data Conversion Tool #02 (0) | 2025.12.14 |
| [C언어] Data Conversion Tool #01 (1) | 2025.12.14 |
| Python 3.14의 주요 개선사항 (0) | 2025.10.09 |
| [Python] 파이썬 2,3 가상환경 사용법 완전 가이드 (0) | 2025.10.03 |

