1. 오늘 한 것
① 한글 인코딩(UTF-8 ↔ CP949 ↔ UTF-16) 구조 통일
- Linux 서버(UTF-8)에서 보낸 한글 데이터가 Windows 클라이언트(CP949)의 로그창에서 깨지는 현상을 해결하기 위해 전 구간 인코딩 파이프라인 구축.
- Windows API의 W 계열 함수(SetWindowTextW 등)가 요구하는 UTF-16(wchar_t) 형식에 맞춰, MultiByteToWideChar / WideCharToMultiByte 함수를 활용한 형변환 유틸리티 구현.
std::wstring Utf8ToWide(const std::string& s) {
if (s.empty()) return L"";
int n = MultiByteToWideChar(CP_UTF8, 0,
s.c_str(), -1, nullptr, 0);
std::wstring w(n, 0);
MultiByteToWideChar(CP_UTF8, 0,
s.c_str(), -1, &w[0], n);
return w;
}
std::string WideToUtf8(const std::wstring& w) {
if (w.empty()) return "";
int n = WideCharToMultiByte(CP_UTF8, 0,
w.c_str(), -1, nullptr, 0, nullptr, nullptr);
std::string s(n, 0);
WideCharToMultiByte(CP_UTF8, 0,
w.c_str(), -1, &s[0], n, nullptr, nullptr);
return s;
}
- C언어 소스파일의 한글 리터럴 깨짐 방지를 위해 gcc -finput-charset=UTF-8 옵션 적용 및 #pragma execution_character_set("utf-8") 선언.
#pragma execution_character_set("utf-8")
- 흐름 최종정리
서버 UTF-8 수신
→ Utf8ToWide() → wstring
→ SetWindowTextW() / AppendLogW()
→ 화면에 정상 출력
입력창에서 wstring 읽기
→ WideToUtf8()
→ send() 로 UTF-8 전송
② WinAPI 기반 비동기 네트워크 UI 스레드 분리
- 문제: recv() 함수는 데이터가 올 때까지 대기(Blocking)하므로, 메인 UI 스레드에서 호출 시 프로그램이 완전히 응답 없음 상태가 됨.
- 해결: 별도의 RecvThread를 생성하여 네트워크 수신을 전담하게 하고, 수신된 데이터를 PostMessage()를 통해 UI 스레드 메시지 큐(WM_APPENDLOG)로 안전하게 전달하는 비동기 아키텍처 구현.
DWORD WINAPI RecvThread(LPVOID) {
NetPacket pkt;
while (true) {
int n = recv(g_sock, (char*)&pkt,
sizeof(pkt), MSG_WAITALL);
if (n <= 0) break;
std::string msg(pkt.payload.chat.message);
// UI 스레드에 메시지 전달 (PostMessage는 스레드 안전)
std::wstring* p = new std::wstring(Utf8ToWide(msg));
PostMessage(g_hWnd, WM_APPENDLOG, 0, (LPARAM)p);
}
return 0;
}
③ 커스텀 UI 컨트롤 드로잉 (WM_PAINT, WM_DRAWITEM)
- 센서 위젯: 상단 온도/습도/움직임 카드는 STATIC 컨트롤을 서브클래싱(CardProc)하여 배경색과 폰트를 상태별로 직접 렌더링.
LRESULT CALLBACK CardProc(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
if (msg == WM_PAINT) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rc; GetClientRect(hWnd, &rc);
// 배경색 (카드별로 다름)
FillRect(hdc, &rc, hBrush);
// 라벨 (작게)
SelectObject(hdc, g_hCardFont);
DrawTextW(hdc, label, -1, &labelRect, DT_CENTER);
// 값 (굵게, 크게)
SelectObject(hdc, g_hCardValFont);
DrawTextW(hdc, value, -1, &valRect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(hWnd, &ps);
return 0;
}
return CallWindowProc(g_origCard, hWnd, msg, wParam, lParam);
}
- 긴급 점멸 배경: 낙상/SOS 발생 시 WM_ERASEBKGND를 오버라이딩하여 배경을 붉게 점멸시킴. 이때 자식 컨트롤(버튼, 로그창)이 같이 깜빡이는 현상을 막기 위해 RedrawWindow에 RDW_NOCHILDREN 플래그를 적용해 부드러운 UI 갱신 구현.
- SOS 버튼: BS_OWNERDRAW 속성과 WM_DRAWITEM 메시지를 활용하여 클릭 시 색상이 어두워지는 직관적인 피드백 버튼 직접 구현.
case WM_DRAWITEM: {
DRAWITEMSTRUCT* dis = (DRAWITEMSTRUCT*)lParam;
if (dis->CtlID == ID_SOS) {
HBRUSH hbr = CreateSolidBrush(
(dis->itemState & ODS_SELECTED)
? RGB(140,10,10) : RGB(210,30,30));
FillRect(dis->hDC, &dis->rcItem, hbr);
DeleteObject(hbr);
SetTextColor(dis->hDC, RGB(255,255,255));
SetBkMode(dis->hDC, TRANSPARENT);
DrawTextW(dis->hDC, L"SOS", -1, &dis->rcItem,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
return TRUE;
}
}
2. 문제 / 헷갈린 점
① 인코딩 지옥의 원인 파악 지연
- 상황: 서버에서 정상적으로 보낸 한글이 UI에서 "거주자 : 안녕하세요" 처럼 박살 나서 출력됨.
- 해결: 세 가지 환경의 기본 인코딩이 전부 다르다는 것을 인지하지 못했음. Linux는 UTF-8, Windows 시스템은 CP949, WinAPI 내부 함수는 UTF-16을 쓴다. **"통신 구간은 무조건 UTF-8로 통일하고, 클라이언트의 화면 출력/입력 직전에만 UTF-16(Wide Char)으로 변환한다"**는 원칙을 세워 해결함.
② 네트워크 스레드와 UI 갱신 충돌
- 상황: RecvThread에서 직접 UI 핸들(SetWindowText)을 건드리려 하니 동작이 불안정해짐.
- 해결: 스레드 간 자원 접근은 위험하다.
// 수신 스레드: new로 메모리를 할당해 포인터를 메시지 큐에 던짐 (PostMessage는 스레드 안전)
std::wstring* p = new std::wstring(Utf8ToWide(msg));
PostMessage(g_hWnd, WM_APPENDLOG, 0, (LPARAM)p);
// UI 스레드 (WndProc): 큐에서 꺼내어 UI를 갱신하고 메모리 해제
case WM_APPENDLOG: {
std::wstring* p = (std::wstring*)lParam;
if (p) {
// ... (로그창 갱신 로직)
delete p;
}
}
③ WM_ERASEBKGND 점멸 시 자식 컨트롤 증발
- 상황: 알람 발생 시 화면을 빨갛게 칠하자, 위에 올려둔 버튼과 로그창이 덮여서 사라지거나 심하게 깜빡임.
- 해결: 화면 무효화 시 자식 영역을 제외하는 플래그 적용.
// 점멸 중: 자식 컨트롤(버튼, 텍스트박스)은 건드리지 않고 배경만 갱신
RedrawWindow(hWnd, &alertRect, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_NOCHILDREN);
3. 오늘 배운 핵심
- 네트워크 통신의 한글 인코딩은 무조건 UTF-8이 표준이다. 플랫폼에 종속된 인코딩(CP949)은 통신 구간에 태우지 말고 엔드포인트에서 형변환(Utf8ToWide)으로 처리해야 한다.
- UI 스레드와 작업 스레드는 철저히 분리해야 한다. 네트워크 수신 같은 블로킹 작업은 별도 스레드로 빼고, PostMessage를 통한 비동기 메시지 전달 방식으로 UI를 갱신해야 창이 멈추지 않는다.
- WinAPI는 날것의 제어권을 제공한다. 버튼 클릭 피드백부터 배경 깜빡임 방지까지 모든 핸들을 직접 제어해야 하지만, 그만큼 OS의 메시지 루프와 화면 렌더링(WM_PAINT) 원리를 투명하게 이해할 수 있다.
4. 다음 목표
- 그동안 작성한 아두이노(센서), 리눅스(서버/DB), 윈도우(클라이언트 UI) 3계층 코드를 모두 통합하여 End-to-End 전체 시스템 테스트 진행.
- 실시간 낙상 감지(초음파) 발생부터 DB alert_logs 인서트, 그리고 클라이언트 화면의 붉은 점멸 및 경고음 발생까지의 딜레이(Latency) 측정 및 최적화.
'포트폴리오 > [IoT 기반 피지컬 AI 교육일지]' 카테고리의 다른 글
| [ C언어 IoT 프로젝트 ] #6 스마트홈 최종 발표 준비 및 회고 (30일차 기록) (0) | 2026.03.27 |
|---|---|
| [ C언어 IoT 프로젝트 ] #5 Win32 API 대시보드 구현 및 최종 기능 점검 (29일차 기록) (0) | 2026.03.24 |
| [ C언어 IoT 프로젝트 ] #3 MySQL 연동 및 멀티스레드 TCP 소켓 구축(27일차 기록) (0) | 2026.03.24 |
| [ C언어 IoT 프로젝트 ] #2 스마트홈 센서 테스트 & 시리얼 통신 (26일차 기록) (0) | 2026.03.23 |
| [ C언어 IoT 프로젝트 ] #1 고령 1인가구 안심 스마트홈 아키텍처 설계 (25일차 기록) (0) | 2026.03.23 |