UE5 소스 분석 - 엔진 진입점 (WinMain → GuardedMain)
Engine/Source/Runtime/Private/Windows/LaunchWindows.cpp

1. 진입점: WinMain
- 언리얼도 익숙한
WinMain함수를 통해 프로그램이 시작된다. - 생각해보면 당연한 건데, 뭔가 특별한 방식일 것이라는 막연한 기대가 있었음. 결국 언리얼도 Windows 애플리케이션이다.
WinMain에서 바로LaunchWindowsStartup함수로 진입.
2. LaunchWindowsStartup
- 윈도우 실행 환경 설정 + 커맨드라인 해석 담당.
- 여러 전처리기(
#ifdef)와 예외 처리가 배치되어 있음 → 실행 모드(에디터/게임/커맨드렛 등)를 미리 분기하는 구조. - 여기서 핵심은 결국
GuardedMain호출.
3. GuardedMain
Engine/Source/Runtime/Private/Launch.cpp
int32 GuardedMain( const TCHAR* CmdLine )
{
// 게임 스레드 시작 및 태깅
// bWaitForDebugger = 디버거 있는지 확인
if (bWaitForDebugger)
{
while (!FPlatformMisc::IsDebuggerPresent())
{
FPlatformProcess::Sleep(0.1f); // 디버거 붙을 때까지 이 스레드 대기
}
if (bWaitForDebuggerAndBreak)
{
UE_DEBUG_BREAK();
}
}
// 메인 초기화 이전에 실행되는 델리게이트 브로드캐스트
// C# 델리게이트 같은 역할. 순서가 중요함
FCoreDelegates::GetPreMainInitDelegate().Broadcast();
// 스코프 객체를 이용한 종료 보장
struct EngineLoopCleanupGuard
{
~EngineLoopCleanupGuard()
{
if (!GUELibraryOverrideSettings.bIsEmbedded)
{
EngineExit();
}
}
} CleanupGuard; // 선언과 동시에 객체 생성
// 메인 함수가 어떤 경로로 끝나든 소멸자에서 EngineExit() 보장
// FEngineLoop 객체에게 초기화 전달
int32 ErrorLevel = EnginePreInit( CmdLine );
// 로딩 진행률 객체
{
// 전체 로딩량을 100이라고 했을 때
FScopedSlowTask SlowTask(100, NSLOCTEXT("EngineInit", "EngineInit_Loading", "Loading..."));
// EnginePreInit이 전체의 80%에 해당하는 작업
SlowTask.EnterProgressFrame(80);
// 나머지 20%는 EngineInit / EditorInit 몫
SlowTask.EnterProgressFrame(20);
```
//엔진 프리 이닛에서 20%의 진행율을 남겨둔채로 끝나니까 이어서 진행하려고.
#if WITH_EDITOR
if (GIsEditor)
{
ErrorLevel = EditorInit(GEngineLoop);
}
else
#endif
{
ErrorLevel = EngineInit();
}
}
// 엔진 시작 시간 측정
// 임베디드 모드라면 내부 틱을 사용하지 않음
if (!GUELibraryOverrideSettings.bIsEmbedded)
{
while( !IsEngineExitRequested() )
{
EngineTick(); // 메인 엔진 루프
}
}
if( GIsEditor )
{
EditorExit();
}
return ErrorLevel;
}흐름 요약
WinMain → LaunchWindowsStartup (환경 설정, 커맨드라인 해석) → GuardedMain
GuardedMain 내부 순서:
- 디버거 대기 처리 (
bWaitForDebugger) PreMainInitDelegate브로드캐스트 (메인 초기화 전 훅)EngineLoopCleanupGuard생성 → 소멸자에서EngineExit()보장 (RAII)EnginePreInit(CmdLine)— 전체 초기화의 80%EditorInit또는EngineInit— 나머지 20% (WITH_EDITOR 분기)while (!IsEngineExitRequested()) { EngineTick(); }— 메인 루프- 에디터라면
EditorExit(), 스코프 종료 시 CleanupGuard가EngineExit()
추가 느낀 점

- 언리얼은 전처리기
#ifdef와 스코프 객체(RAII)를 활용해 측정/태깅 지점을 코드 곳곳에 심어두었다. EngineLoopCleanupGuard,FScopedSlowTask모두 같은 패턴 — 생성자/소멸자를 진입/이탈 훅으로 쓰는 방식.- 이전 프로젝트에서 ms 측정하려고 스코프 안에 타이머 객체 넣어두었던 것과 같은 원리. 상용 엔진도 결국 같은 C++ 관용구 위에서 돌아간다.