ETC./Project Archive

SkaldLogger: MFC GUI를 위한 투명 오버레이 로거 개발 기록

PixelMechanic 2025. 11. 2. 16:30

Vision & Inspection, Motion & Control 장비의 HMI(제어 GUI)를 개발하다 보면, 수많은 컴포넌트의 상태를 실시간으로 확인해야 할 때가 많습니다.

1. 프로젝트의 목표: GUI와 로그의 공존

디버깅 시 로그를 확인하는 기존 방식은 번거롭습니다.

  1. AfxMessageBox: 프로그램을 일시 정지시키고 GUI를 가립니다.
  2. 별도의 로그 다이얼로그: 메인 GUI의 중요 부분을 가려서, 로그를 볼 때마다 창을 옮겨야 합니다.
  3. Visual Studio 출력 창: (OutputDebugString) GUI와 시선이 분리되어 실시간성이 떨어집니다.

이 문제를 해결하기 위해, "기존 제어 GUI를 가리지 않고, GUI를 조작하는 동시에 실시간 로그를 확인할 수 있는 투명 오버레이" 로거를 개발하는 것을 목표로 삼았습니다.

 

여기서 영화 '매트릭스(Matrix)'의 투명한 배경 위로 코드가 흐르는 장면에서 영감을 얻어, MFC DLL 기반의 실시간 오버레이 로깅 툴, SkaldLogger 프로젝트를 시작하게 되었습니다.

2. SkaldLogger: 시스템의 이야기꾼 (컨셉)

(TMI: 저희 팀은 개발 툴에 라그나로크 신화의 이름을 붙이는 전통이 있습니다.)

Skald(스칼드)는 북유럽 신화에서 역사를 기록하고 읊던 시인을 의미합니다. SkaldLogger는 복잡한 장비 시스템의 내부 동작과 이벤트를 실시간으로 기록하고 개발자에게 전달하는 '시스템의 이야기꾼' 역할을 수행한다는 컨셉으로 명명되었습니다.

3. 핵심 구현 내용 (MFC DLL)

SkaldLogger는 어떤 MFC 호스트 애플리케이션에서도 쉽게 로드할 수 있도록 MFC Regular DLL 형태로 구현을 진행했습니다.

A. 투명 + 스크롤링 윈도우 구현 (Win32 API & DirectX)

가장 핵심적인 기능입니다.

  • 투명 윈도우: CWnd 기반 윈도우 생성 시, 확장 스타일로 WS_EX_LAYERED 를 적용합니다.
  • 스크롤 및 애니메이션: OnPaint() 이벤트에서 GDI+ 라이브러리를 사용해 텍스트를 직접 그립니다. SetTimer를 이용해 주기적으로 로그 메시지의 Y좌표와 Alpha(투명도) 값을 업데이트하여, 로그가 아래에서 위로 부드럽게 스크롤되며 페이드-인/아웃 되도록 구현했습니다.
  • 로그 버퍼 관리: std::deque 컨테이너를 사용하여 최대 100줄의 로그만 유지하고, 새 로그가 추가될 때 가장 오래된 로그는 자동으로 제거(pop)되도록 했습니다.

B. API 설계: 퍼사드 패턴 (Facade Pattern)

호스트 앱에서의 사용 편의성을 위해, 복잡한 내부 로직은 숨기고 간단한 인터페이스만 노출하도록 설계했습니다.

해야 할 때가 많습니다.

클래스 역할 특징
CSkaldLog 내부 핵심 로직 (파일 I/O, 윈도우 관리, 싱글톤) 외부 노출 안 됨
SkaldLogger 외부 인터페이스 (API) SKALDLOGGER_API 로 노출. 호스트 앱은 이 클래스만 사용.

 

이 설계 덕분에 호스트 애플리케이션 개발자는 CSkaldLog의 복잡한 윈도우 핸들링이나 DirectX 구현을 알 필요 없이, SkaldLogger::GetInstance().LogInfo(...) 같은 간단한 호출만으로 모든 기능을 사용할 수 있습니다.

4. SkaldLogger 간단 사용법 (API)

호스트 애플리케이션(exe)에서 DLL의 기능을 사용하는 방법은 매우 간단합니다.

 

// 1. 초기 설정 (App의 OnInitDialog 등)
void CSkaldHostAppDlg::OnInitDialog()
{
    // ...

    // 로그를 수신할 콜백 함수 등록 (필수)
    SkaldLogger::GetInstance().SetLogCallback(MyLogCallbackFunction); 

    // 투명 오버레이 윈도우 생성 및 위치 지정
    // 예: (50, 50) 위치에서 (850, 200) 크기로 메인 GUI 위에 표시
    CRect overlayRect(50, 50, 850, 200);
    SkaldLogger::GetInstance().SetOverlayWindow(this, overlayRect);

    // (선택) DLL 내부 GUI를 통해 파일명 선택
    std::string filename = SkaldLogger::GetInstance().SelectLogFileViaDialog(m_hWnd);

    if (!filename.empty()) {
        SkaldLogger::GetInstance().SetLogFile(filename);
    }
    
    // ...
}

// 2. 로그 기록 (프로그램 어디서든)
void MySystemClass::PerformTask()
{
    // 로그 레벨별 함수 호출
    SkaldLogger::GetInstance().LogInfo("작업 시작.");
    
    if (errorCondition) {
        SkaldLogger::GetInstance().LogError("치명적 오류 발생!");
    } else {
        SkaldLogger::GetInstance().LogDebug("디버깅 데이터: [X=10]");
    }
}

프로젝트 회고 (맺음말)

SkaldLogger는 비록 실제 장비에 최종 적용되지는 않았지만, 복잡한 제어 GUI 환경에서 디버깅 편의성을 높이기 위한 시도였습니다.

 

복잡한 MFC 환경에서 Win32의 고급 기능(WS_EX_LAYERED)과 C++ 표준 기능(std::deque)을 융합하여 실제 현장의 불편함을 해결하려 했던 이 아이디어와 구현 과정을 'Project Archive'에 기록으로 남깁니다.

비슷한 고민을 하는 다른 개발자분들에게 이 아이디어가 참고가 되기를 바랍니다.

 

소스코드도 함께 남깁니다. 별로 복잡하거나 참고 하실분은 참고 해주시고, 출처만 잘 남겨주세요 

SkaldLog.zip
0.16MB

 

 

실제 작동 이미지 Dialog 에도 이벤트가 접근됨!

 

반응형