[Unreal Engine] Reflection System - Generation 2

Imeamangryang·2025년 6월 24일

Unreal Reflection System

목록 보기
3/10
post-thumbnail

출처 : staticJPL - Unreal-Engine-Core-Documentation


Type System Code Generation

UHT가 타입 메타데이터 정보를 충분히 분석하고 획득했다면, 다음 단계는 이 정보를 활용해 앞서 설명한 타입 시스템 구조를 프로그램의 메모리 내에 실제로 구성하는 것입니다. 이 절차적 단계는 "등록(registration)"이라 하며, 이후 별도의 섹션에서 더 자세히 설명합니다.

핵심 개념:

일반적인 프로그램의 빌드 과정이 전처리, 컴파일, 어셈블, 링킹 등 여러 단계를 거치는 것처럼, 언리얼 엔진도 메모리 내에서의 타입 시스템 구축 과정을 다음과 같이 개념화합니다: 생성(generation), 수집(collection), 등록(registration), 그리고 연결(linking).

생성된 매크로의 해부 : UClass, UFunction, UProperty, UEnum & UInterface

#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MyClass.generated.h"
/**
*
*/
UCLASS()
class REFLECTIONTEST_API UMyClass : public UObject
{
    GENERATED_BODY()
public:
    UPROPERTY()
    int Health;
    UFUNCTION(BlueprintCallable, Category = "Hello")
    void CallableFunc();
    UFUNCTION(BlueprintNativeEvent, Category = "Hello")
    void NativeFunc();
    UFUNCTION(BlueprintImplementableEvent, Category = "Hello")
    void ImplementableFunc();
};

UENUM()
enum EMyEnum : int
{
    E_Num1,
    E_Num2,
    E_Num3,
};

USTRUCT()
struct REFLECTIONTEST_API FMyStruct
{
    GENERATED_USTRUCT_BODY();
    UPROPERTY()
    float Score;
};

UINTERFACE(BlueprintType)
class REFLECTIONTEST_API UMyInterface : public UInterface
{
    GENERATED_UINTERFACE_BODY()
};
class IMyInterface
{
    GENERATED_IINTERFACE_BODY()
public:
    UFUNCTION(BlueprintImplementableEvent)
    void BPFunc() const;
};

UClass 해부

GENERATED_BODY() - 이 매크로는 메타데이터 정의와 함께 주요 선언부를 생성하며, ObjectMacros.h 689번째 줄(UE 5.2 기준)에서 확인할 수 있습니다.

// GENERATED_BODY()와 GENERATED_USTRUCT_BODY() 구현을 돕는 매크로 쌍
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
// 생성된 코드 블록 끝에 불필요한 세미콜론을 포함하여, 
// intellisense 파서가 줄 번호/생성 코드가 오래된 경우에도 새 선언을 파싱할 수 있도록 함
#define GENERATED_BODY_LEGACY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY_LEGACY);
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY)

BODY_MACRO_COMBINE 매크로란?
BODY_MACRO_COMBINE_INNER는 전달된 매개변수들을 하나의 문자열로 이어붙이는 역할을 합니다. 예를 들어,

FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_13_PROLOG 와 같은 결과가 생성됩니다.
형식은 CURRENT_FILE_ID_12_PROLOG와 같으며, 여기서 12는 UCLASS가 작성된 실제 파일의 줄 번호입니다.
즉, CURRENT_FILE_ID는 BODY_MACRO_COMBINE이 호출되기 전에 치환됩니다.
매크로의 메타데이터 파라미터에서 키워드와 지정자에 대한 세부 설명은 생략합니다.

MyClass.generated.h

시작 부분에는 #pragma once가 정의되지 않은 경우를 대비해 MyClass.generated.h가 중복 포함되는 것을 방지하는 #ifdef 전처리 지시문이 있습니다.

PRAGMA_DISABLE_DEPRECATION_WARNINGS
#ifdef REFLECTIONTEST_MyClass_generated_h
#error "MyClass.generated.h already included, missing '#pragma once' in MyClass.h"
#endif
#define REFLECTIONTEST_MyClass_generated_h


#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_ACCESSORS
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_CALLBACK_WRAPPERS
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_INCLASS_NO_PURE_DECLS
private:
    static void StaticRegisterNativesUMyClass();
    friend struct Z_Construct_UClass_UMyClass_Statics;
public:
    DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/ReflectionTest"), NO_API)
    DECLARE_SERIALIZER(UMyClass)
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_INCLASS
private:
    static void StaticRegisterNativesUMyClass();
    friend struct Z_Construct_UClass_UMyClass_Statics;
public:
    DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/ReflectionTest"), NO_API)
    DECLARE_SERIALIZER(UMyClass)
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_STANDARD_CONSTRUCTORS
    /** Standard constructor, called after all reflected properties have been initialised */
    NO_API UMyClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyClass)
    DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyClass);
    DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyClass);
private:
    /** Private move- and copy-constructors, should never be used */
    NO_API UMyClass(UMyClass&&);
    NO_API UMyClass(const UMyClass&);
public:
    NO_API virtual ~UMyClass();
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_ENHANCED_CONSTRUCTORS
    /** Standard constructor, called after all reflected properties have been initialised */
    NO_API UMyClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
private:
    /** Private move- and copy-constructors, should never be used */
    NO_API UMyClass(UMyClass&&);
    NO_API UMyClass(const UMyClass&);
public:
    DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyClass);
    DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyClass);
    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyClass)
    NO_API virtual ~UMyClass();
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_28_PROLOG
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_GENERATED_BODY_LEGACY
PRAGMA_DISABLE_DEPRECATION_WARNINGS
public:
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_SPARSE_DATA
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_RPC_WRAPPERS
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_ACCESSORS
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_CALLBACK_WRAPPERS
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_INCLASS
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_STANDARD_CONSTRUCTORS
public:
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_GENERATED_BODY
PRAGMA_DISABLE_DEPRECATION_WARNINGS
public:
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_SPARSE_DATA
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_RPC_WRAPPERS_NO_PURE_DECLS
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_ACCESSORS
  FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_CALLBACK_WRAPPERS
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_INCLASS_NO_PURE_DECLS
    FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_31_ENHANCED_CONSTRUCTORS
private:
PRAGMA_ENABLE_DEPRECATION_WARNINGS

CTRL+F Definitions:

INCLASS_DECLS
INCLASS_NO_PURE_DECLS

#define DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(TClass)
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X);


DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(TClass)

이 매크로는 매우 중요합니다. C++에서는 네이티브 C++ 생성자에 직접 함수 포인터를 지정할 수 없기 때문입니다. 언리얼 엔진은 이 한계를 해결하기 위해 일반 함수 포인터로 생성자 래퍼를 도입합니다. 모든 생성자는 동일한 시그니처를 가지며, UClass 내부의 함수 포인터에 저장됩니다.

DECLARE_CLASS

DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/ReflectionTest"), NO_API)  
DECLARE_SERIALIZER(UMyClass)

Declare Class 매크로는 UClass의 뼈대를 정의하고 StaticClass() 함수의 위치를 지정하는 매우 중요한 역할을 합니다. 이 매크로의 주요 파라미터는 다음과 같습니다.

  • TClass: 클래스 이름
  • TSuperClass: 부모(기반) 클래스 이름
  • TStaticFlags: 클래스의 속성 플래그(0이면 기본 상태). EClassFlags 열거형을 참고하면 다양한 정의를 확인할 수 있습니다.
  • TStaticCastFlags: 이 클래스가 변환될 수 있는 대상 클래스 지정(0이면 기본 클래스 변환 없음). EClassCastFlags를 참고하면 지원되는 변환 목록을 볼 수 있습니다.
  • TPackage: 클래스가 속한 패키지 이름. 모든 오브젝트는 반드시 패키지에 소속되어야 하며, 각 패키지는 고유한 이름을 가집니다. 예시의 "/Script/ReflectionTest"는 Script(사용자 구현 영역) 아래의 ReflectionTest 패키지를 의미합니다. Script는 C++ 또는 블루프린트 등 사용자의 구현 영역을 나타내며, ReflectionTest는 프로젝트 이름입니다. 이 프로젝트 내에 정의된 모든 오브젝트는 해당 패키지에 속하게 됩니다. 패키지는 이후 오브젝트를 조직화하는 단위로 활용됩니다.
  • TRequiredAPI: DLL import/export를 위한 마크입니다. 예시의 NO_API는 최종 실행 파일에서 별도의 export가 필요 없음을 의미합니다.
/*-----------------------------------------------------------------------------
Class declaration macros.
-----------------------------------------------------------------------------*/
#define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI )
private:
	TClass& operator=(TClass&&);
	TClass& operator=(const TClass&);
	TRequiredAPI static UClass* GetPrivateStaticClass();
public:
	/** Bitwise union of #EClassFlags pertaining to this class.*/
	static constexpr EClassFlags StaticClassFlags=EClassFlags(TStaticFlags);
	/** Typedef for the base class ({{ typedef-type }}) */
	typedef TSuperClass Super;
	/** Typedef for {{ typedef-type }}. */
	typedef TClass ThisClass;
/** Returns a UClass object representing this class at runtime */
inline static UClass* StaticClass()
{
	return GetPrivateStaticClass();
}
/** Returns the package this class belongs in */
inline static const TCHAR* StaticPackage()
{
	return TPackage;
}
/** Returns the static cast flags for this class */
inline static EClassCastFlags StaticClassCastFlags()
{
	return TStaticCastFlags;
}
/** For internal use only; use StaticConstructObject() to create new objects. */
inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter =
(UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags)
{
	return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags);
	}
	/** For internal use only; use StaticConstructObject() to create new objects. */
	inline void* operator new( const size_t InSize, EInternal* InMem )
	{
	return (void*)InMem;
	}
	/* Eliminate V1062 warning from PVS-Studio while keeping MSVC and Clang happy. */
	inline void operator delete(void* InMem)
	{
	::operator delete(InMem);
    }

MyClass.generated.cpp

#include "UObject/GeneratedCppIncludes.h"
#include "ReflectionTest/MyClass.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void EmptyLinkFunctionForGeneratedCodeMyClass() {}
// Cross Module References
	COREUOBJECT_API UClass* Z_Construct_UClass_UInterface();
	COREUOBJECT_API UClass* Z_Construct_UClass_UObject(); // Makes Sure UObject's UClass is registered
	REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyClass(); // Reference to UObject's UClass.
	REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyClass_NoRegister(); // Construct UClass object for UMyClass (without
	registering).
	REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyInterface();
	REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyInterface_NoRegister();
	REFLECTIONTEST_API UEnum* Z_Construct_UEnum_ReflectionTest_EMyEnum();
	REFLECTIONTEST_API UScriptStruct* Z_Construct_UScriptStruct_FMyStruct();
	UPackage* Z_Construct_UPackage__Script_ReflectionTest(); // Construct UPackage for MyClass, I think this is Unused. There
	is no implementation.U
// End Cross Module References
void UMyClass::StaticRegisterNativesUMyClass()
{
	UClass* Class = UMyClass::StaticClass();
	static const FNameNativePtrPair Funcs[] = {
		{ "CallableFunc", &UMyClass::execCallableFunc },
		{ "NativeFunc", &UMyClass::execNativeFunc },
};
FNativeFunctionRegistrar::RegisterFunctions(Class, Funcs, UE_ARRAY_COUNT(Funcs));
}

IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(UMyClass); // Important Used to Implement UCLASS

UClass* Z_Construct_UClass_UMyClass_NoRegister()
{
return UMyClass::StaticClass(); // Here gives us Direct Access to UClass Object
}

UClass* Z_Construct_UClass_UMyClass()
{
	if (!Z_Registration_Info_UClass_UMyClass.OuterSingleton)
	{
	UECodeGen_Private::ConstructUClass(Z_Registration_Info_UClass_UMyClass.OuterSingleton,
	Z_Construct_UClass_UMyClass_Statics::ClassParams);
	}
	return Z_Registration_Info_UClass_UMyClass.OuterSingleton;
}


void ConstructUClass(UClass*& OutClass, const FClassParams& Params)
{
	if (OutClass && (OutClass->ClassFlags & CLASS_Constructed)) // Prevents Duplicate Registration
	{
	    return;
    }
	/**
	The For Loop below,
	Can see the Array of Function pointers are called for Macros Z_Construct_UClass_UObject
	Z_Construct_UPackage__Script Macro is also called to created UPackage
	UObject* (*const Z_Construct_UClass_UMyClass_Statics::DependentSingletons[])() = {
	(UObject* (*)())Z_Construct_UClass_UObject,
	(UObject* (*)())Z_Construct_UPackage__Script_ReflectionTest,
	};
	*/
	for (UObject* (*const *SingletonFunc)() = Params.DependencySingletonFuncArray, *(*const *SingletonFuncEnd)() =
	SingletonFunc + Params.NumDependencySingletons; SingletonFunc != SingletonFuncEnd; ++SingletonFunc)
	{
		(*SingletonFunc)();
	}
	// Params.ClassNoRegister comes from UECodeGen_Private::FClassParams
	// &UMyClass::StaticClass
	// This is the Function Pointer that gets us UMyClass Object which is called
	// by &UMyClass::StaticClass() at this point ::StaticClass() is already defined
	UClass* NewClass = Params.ClassNoRegisterFunc();
	OutClass = NewClass;
	if (NewClass->ClassFlags & CLASS_Constructed)
	{
		return;
	}
	UObjectForceRegistration(NewClass); // Register itself to extract information.
	UClass* SuperClass = NewClass->GetSuperClass();
	if (SuperClass)
	{
		NewClass->ClassFlags |= (SuperClass->ClassFlags & CLASS_Inherit);
	}
	// This Adds Class_Constructed Flags and Required API Flags
	NewClass->ClassFlags |= (EClassFlags)(Params.ClassFlags | CLASS_Constructed);
	// Make sure the reference token stream is empty since it will be reconstructed later on
	// This should not apply to intrinsic classes since they emit native references before AssembleReferenceTokenStream is called.
	
	if ((NewClass->ClassFlags & CLASS_Intrinsic) != CLASS_Intrinsic)
	{
		check((NewClass->ClassFlags & CLASS_TokenStreamAssembled) != CLASS_TokenStreamAssembled);
		NewClass->ReferenceTokens.Reset();
	}
	/**
	CreateLinkAndAddChildFunctionsToMap Creates the Function Pointer for Native Functions
	of this class. This then adds native functions to an internal native Function Table
	with a unicode name. This is used when generating code from blueprints.
	This struct maps a string anme to a native function.
	FPropertyParamsBase is the struct, we basically create Property pointers, See Collection Phase
	*/
	NewClass->CreateLinkAndAddChildFunctionsToMap(Params.FunctionLinkArray, Params.NumFunctions);
	/**
	Inside UObjectGlobals.cpp Line 5552 we see this function inside namespace UECodeGen_Private
	Creates the Prop objects. FProperties are Inherited from FField.
	*/
	ConstructFProperties(NewClass, Params.PropertyArray, Params.NumProperties);
	if (Params.ClassConfigNameUTF8)
	{
		NewClass->ClassConfigName = FName(UTF8_TO_TCHAR(Params.ClassConfigNameUTF8));
	}
	// Checks if it's abstract class
	NewClass->SetCppTypeInfoStatic(Params.CppClassInfo);
	if (int32 NumImplementedInterfaces = Params.NumImplementedInterfaces)
	{
		NewClass->Interfaces.Reserve(NumImplementedInterfaces);
		for (const FImplementedInterfaceParams* ImplementedInterface = Params.ImplementedInterfaceArray,
		*ImplementedInterfaceEnd = ImplementedInterface + NumImplementedInterfaces; ImplementedInterface != ImplementedInterfaceEnd;
		++ImplementedInterface)
		{
			UClass* (*ClassFunc)() = ImplementedInterface->ClassFunc;
			UClass* InterfaceClass = ClassFunc ? ClassFunc() : nullptr;
			NewClass->Interfaces.Emplace(InterfaceClass, ImplementedInterface->Offset, ImplementedInterface-
			>bImplementedByK2);
		}
	}
	#if WITH_METADATA
	AddMetaData(NewClass, Params.MetaDataArray, Params.NumMetaData);
	#endif
	// Perform "static" linking, Wrapper Function UStruct::Link(..)
	NewClass->StaticLink();
	// Uses some SideCar Design pattern to extend functionality of the USuperStruct?
	NewClass->SetSparseClassDataStruct(NewClass->GetSparseClassDataArchetypeStruct());
}

매크로 IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(UMyClass)는 클래스 정보를 GetPrivateStaticClassBody 함수로 전달하는 역할을 합니다. 이 함수는 UMyClass 타입의 스켈레톤 객체를 생성하는 책임을 집니다. 여기서 설명하는 내용은 CPP에서 DECLARE_CLASS() 매크로가 실제로 어떻게 구현되는지에 대한 부분입니다.

// Implement the GetPrivateStaticClass and the registration info but do not auto register the class.
// This is primarily used by UnrealHeaderTool
#define IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(TClass)
FClassRegistrationInfo Z_Registration_Info_UClass_##TClass;
UClass* TClass::GetPrivateStaticClass()
{
if (!Z_Registration_Info_UClass_##TClass.InnerSingleton)
{
	/* this could be handled with templates, but we want it external to avoid code bloat */
	GetPrivateStaticClassBody(
		StaticPackage(),
		(TCHAR*)TEXT(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0),
		Z_Registration_Info_UClass_##TClass.InnerSingleton,
		StaticRegisterNatives##TClass,
		sizeof(TClass),
		alignof(TClass),
		TClass::StaticClassFlags,
		TClass::StaticClassCastFlags(),
		TClass::StaticConfigName(),
		(UClass::ClassConstructorType)InternalConstructor<TClass>,
		(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>,
		UOBJECT_CPPCLASS_STATICFUNCTIONS_FORCLASS(TClass),
		&TClass::Super::StaticClass,
		&TClass::WithinClass::StaticClass
		);
	}
	return Z_Registration_Info_UClass_##TClass.InnerSingleton;
}

Expanded MyClass.h

class REFLECTIONTEST_API UMyClass : public UObject
{
private:
	static void StaticRegisterNativesUMyClass();
	friend struct Z_Construct_UClass_UMyClass_Statics;
private:
	TClass& operator=(TClass&&);
	TClass& operator=(const TClass&);
	TRequiredAPI static UClass* GetPrivateStaticClass();
public:
	/** Bitwise union of #EClassFlags pertaining to this class.*/
	static constexpr EClassFlags StaticClassFlags=EClassFlags(TStaticFlags);
	/** Typedef for the base class ({{ typedef-type }}) */
	typedef TSuperClass Super;
	/** Typedef for {{ typedef-type }}. */
	typedef TClass ThisClass;
	/** Returns a UClass object representing this class at runtime */
inline static UClass* StaticClass()
{
	return GetPrivateStaticClass();
}
/** Returns the package this class belongs in */
inline static const TCHAR* StaticPackage()
{
	return TPackage;
}
/** Returns the static cast flags for this class */
inline static EClassCastFlags StaticClassCastFlags()
{
	return TStaticCastFlags;
}
/** For internal use only; use StaticConstructObject() to create new objects. */
inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter =
(UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags)
{
	return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags);
}
/** For internal use only; use StaticConstructObject() to create new objects. */
inline void* operator new( const size_t InSize, EInternal* InMem )
{
	return (void*)InMem;
}
/* Eliminate V1062 warning from PVS-Studio while keeping MSVC and Clang happy. */
inline void operator delete(void* InMem)
{
	::operator delete(InMem);
}
// Serializer Macro
friend FArchive &operator<<( FArchive& Ar, TClass*& Res )
{
	return Ar << (UObject*&)Res;
}
friend void operator<<(FStructuredArchive::FSlot InSlot, TClass*& Res)
{
	InSlot << (UObject*&)Res;
}
7/** Standard constructor, called after all reflected properties have been initialized */
NO_API UMyClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }

private: /** Private move- and copy-constructors, should never be used */ NO_API UMyClass(UMyClass&&); NO_API
    UMyClass(const UMyClass&);

Expanded MyClass.Generated.cpp

// Cross Module References CoreUObject
COREUOBJECT_API UClass* Z_Construct_UClass_UObject();
REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyClass();
REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyClass_NoRegister();
UPackage* Z_Construct_UPackage__Script_ReflectionTest();
void EmptyLinkFunctionForGeneratedCodeMyClass() {}
DEFINE_FUNCTION(UMyClass::execNativeFunc)
{
	P_FINISH;
	P_NATIVE_BEGIN;
	P_THIS->NativeFunc_Implementation();
	P_NATIVE_END;
}
DEFINE_FUNCTION(UMyClass::execCallableFunc)
{
	P_FINISH;
	P_NATIVE_BEGIN;
	P_THIS->CallableFunc();
	P_NATIVE_END;
}
static FName NAME_UMyClass_ImplementableFunc = FName(TEXT("ImplementableFunc"));
void UMyClass::ImplementableFunc()
{
	ProcessEvent(FindFunctionChecked(NAME_UMyClass_ImplementableFunc),NULL);
}
static FName NAME_UMyClass_NativeFunc = FName(TEXT("NativeFunc"));
void UMyClass::NativeFunc()
{
	ProcessEvent(FindFunctionChecked(NAME_UMyClass_NativeFunc),NULL);
}
void UMyClass::StaticRegisterNativesUMyClass()
{
	UClass* Class = UMyClass::StaticClass();
	static const FNameNativePtrPair Funcs[] = {
	{ "CallableFunc", &UMyClass::execCallableFunc },
	{ "NativeFunc", &UMyClass::execNativeFunc },
	};
	FNativeFunctionRegistrar::RegisterFunctions(Class, Funcs, UE_ARRAY_COUNT(Funcs));
}
// Define FuncParameters and Metadata Params
struct Z_Construct_UFunction_UMyClass_CallableFunc_Statics
{
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];
	#endif
	static const UECodeGen_Private::FFunctionParams FuncParams;
};
// Static Defines for the Collection Phase
struct Z_Construct_UClass_UMyClass_Statics
{
	static UObject* (*const DependentSingletons[])();
	static const FClassFunctionLinkInfo FuncInfo[];
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Class_MetaDataParams[];
	#endif
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam NewProp_Health_MetaData[];
	#endif
	static const UECodeGen_Private::FUnsizedIntPropertyParams NewProp_Health;
	static const UECodeGen_Private::FPropertyParamsBase* const PropPointers[];
	static const FCppClassTypeInfoStatic StaticCppClassTypeInfo;
	static const UECodeGen_Private::FClassParams ClassParams;
};

UClass* TClass::GetPrivateStaticClass(){
if (!Z_Registration_Info_UClass_##TClass.InnerSingleton)
{
	/* this could be handled with templates, but we want it external to avoid code bloat */
	GetPrivateStaticClassBody(
	StaticPackage(),
	(TCHAR*)TEXT(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0),
	Z_Registration_Info_UClass_##TClass.InnerSingleton,
	StaticRegisterNatives##TClass,
	sizeof(TClass),
	alignof(TClass),
	TClass::StaticClassFlags,
	TClass::StaticClassCastFlags(),
	TClass::StaticConfigName(),
	(UClass::ClassConstructorType)InternalConstructor<TClass>,
	(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>,
	UOBJECT_CPPCLASS_STATICFUNCTIONS_FORCLASS(TClass),
	&TClass::Super::StaticClass,
	&TClass::WithinClass::StaticClass
	);
}
	return Z_Registration_Info_UClass_##TClass.InnerSingleton;
}

UClass* Z_Construct_UClass_UMyClass_NoRegister()
{
	return UMyClass::StaticClass();
}
UObject* (*const Z_Construct_UClass_UMyClass_Statics::DependentSingletons[])() = {
	(UObject* (*)())Z_Construct_UClass_UObject,
	(UObject* (*)())Z_Construct_UPackage__Script_ReflectionTest,
};
const FClassFunctionLinkInfo Z_Construct_UClass_UMyClass_Statics::FuncInfo[] = {
	{ &Z_Construct_UFunction_UMyClass_CallableFunc, "CallableFunc" }, // 2296517141
	{ &Z_Construct_UFunction_UMyClass_ImplementableFunc, "ImplementableFunc" }, // 1412324750
	{ &Z_Construct_UFunction_UMyClass_NativeFunc, "NativeFunc" }, // 1217410402
};

#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam Z_Construct_UClass_UMyClass_Statics::Class_MetaDataParams[] = {
	{ "IncludePath", "MyClass.h" },
	{ "ModuleRelativePath", "MyClass.h" },
	{ "ObjectInitializerConstructorDeclared", "" },
};
#endif
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam Z_Construct_UClass_UMyClass_Statics::NewProp_Health_MetaData[] = {
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
const FClassRegisterCompiledInInfo
Z_CompiledInDeferFile_FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_Statics::ClassInfo[]
= {
	{ Z_Construct_UClass_UMyInterface, UMyInterface::StaticClass, TEXT("UMyInterface"),
	&Z_Registration_Info_UClass_UMyInterface, CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(UMyInterface),
	4078863325U) },
	{ Z_Construct_UClass_UMyClass, UMyClass::StaticClass, TEXT("UMyClass"), &Z_Registration_Info_UClass_UMyClass,
	CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(UMyClass), 138299640U) },
};

Reflection.Test.init.gen.cpp

ConstructUClass 과정에서 리플렉션 코드 내 (*SingletonFunc)->Z_Registration_Info_UPackage__Script_ReflectionTest() 호출은 ReflectionTest 프로젝트의 UPackage 생성을 시작합니다.

#include "UObject/GeneratedCppIncludes.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void EmptyLinkFunctionForGeneratedCodeReflectionTest_init() {}
static FPackageRegistrationInfo Z_Registration_Info_UPackage__Script_ReflectionTest;
FORCENOINLINE UPackage* Z_Construct_UPackage__Script_ReflectionTest()
{
	if (!Z_Registration_Info_UPackage__Script_ReflectionTest.OuterSingleton)
	{
		static const UECodeGen_Private::FPackageParams PackageParams = {
		"/Script/ReflectionTest",
		nullptr,
		0,
		PKG_CompiledIn | 0x00000000,
		0x68D52929,
		0x02EA1D27,
		METADATA_PARAMS(nullptr, 0)
		};
		UECodeGen_Private::ConstructUPackage(Z_Registration_Info_UPackage__Script_ReflectionTest.OuterSingleton,
		PackageParams);
	}
	return Z_Registration_Info_UPackage__Script_ReflectionTest.OuterSingleton;
}
static FRegisterCompiledInInfo
Z_CompiledInDeferPackage_UPackage__Script_ReflectionTest(Z_Construct_UPackage__Script_ReflectionTest,
TEXT("/Script/ReflectionTest"), Z_Registration_Info_UPackage__Script_ReflectionTest,
CONSTRUCT_RELOAD_VERSION_INFO(FPackageReloadVersionInfo, 0x68D52929, 0x02EA1D27));
PRAGMA_ENABLE_DEPRECATION_WARNINGS

GetPrivateStaticClassBody는 UClass의 스캐폴딩 역할을 합니다. 핫 리로드(hot reload)를 위해 패키지를 가져오고, 이후 InitializePrivateStaticClass가 호출됩니다. 이때 UClass의 OuterPrivate 멤버 변수는 게임 모듈의 패키지에 의해 설정됩니다. 중요한 점은, 실제로 이 값이 생성되어 할당되는 시점은 Object->Deferred가 호출될 때라는 것입니다. 이 과정에서 GetPrivateStaticClass는 함수에 대한 참조를 전달받게 됩니다.


UEnum 해부

UENUM()  
enum EMyEnum : int  
{  
    E_Num1,  
    E_Num2,  
    E_Num3,  
};

EMyEnum.generated.h

#define FOREACH_ENUM_EMYENUM(op) op(E_Num1) op(E_Num2) op(E_Num3)  
enum EMyEnum : int;  
template<> REFLECTIONTEST_API UEnum* StaticEnum<EMyEnum>();

EMyEnum.generated.cpp

static FEnumRegistrationInfo Z_Registration_Info_UEnum_EMyEnum;
	static UEnum* EMyEnum_StaticEnum()
	{
		if (!Z_Registration_Info_UEnum_EMyEnum.OuterSingleton)
		{
			Z_Registration_Info_UEnum_EMyEnum.OuterSingleton = GetStaticEnum(Z_Construct_UEnum_ReflectionTest_EMyEnum,
			(UObject*)Z_Construct_UPackage__Script_ReflectionTest(), TEXT("EMyEnum"));
		}
		return Z_Registration_Info_UEnum_EMyEnum.OuterSingleton;
	}
	template<> REFLECTIONTEST_API UEnum* StaticEnum<EMyEnum>()
	{
		return EMyEnum_StaticEnum();
	}
	struct Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics
	{
		static const UECodeGen_Private::FEnumeratorParam Enumerators[];
		#if WITH_METADATA
		static const UECodeGen_Private::FMetaDataPairParam Enum_MetaDataParams[];
		#endif
		static const UECodeGen_Private::FEnumParams EnumParams;
	};
	const UECodeGen_Private::FEnumeratorParam Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::Enumerators[] = {
		{ "E_Num1", (int64)E_Num1 },
		{ "E_Num2", (int64)E_Num2 },
		{ "E_Num3", (int64)E_Num3 },
	};
	#if WITH_METADATA
	const UECodeGen_Private::FMetaDataPairParam Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::Enum_MetaDataParams[] = {
		{ "E_Num1.Name", "E_Num1" },
		{ "E_Num2.Name", "E_Num2" },
		{ "E_Num3.Name", "E_Num3" },
		{ "ModuleRelativePath", "MyClass.h" },
	};
	#endif
	const UECodeGen_Private::FEnumParams Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::EnumParams = {
		(UObject*(*)())Z_Construct_UPackage__Script_ReflectionTest,
		nullptr,
		"EMyEnum",
		"EMyEnum",
		Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::Enumerators,
		UE_ARRAY_COUNT(Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::Enumerators),
		RF_Public|RF_Transient|RF_MarkAsNative,
		EEnumFlags::None,
		(uint8)UEnum::ECppForm::Regular,
		METADATA_PARAMS(Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::Enum_MetaDataParams,
		UE_ARRAY_COUNT(Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::Enum_MetaDataParams))
		
		};
UEnum* Z_Construct_UEnum_ReflectionTest_EMyEnum()
{
	if (!Z_Registration_Info_UEnum_EMyEnum.InnerSingleton)
	{
		UECodeGen_Private::ConstructUEnum(Z_Registration_Info_UEnum_EMyEnum.InnerSingleton,
		Z_Construct_UEnum_ReflectionTest_EMyEnum_Statics::EnumParams);
	}
	return Z_Registration_Info_UEnum_EMyEnum.InnerSingleton;
}

MyEnum의 구조는 Z_Construct_UEnum_ReflectionTest_EMyEnum() 함수 외에 추가적인 함수가 없습니다. 상단부터 살펴보면,

static FEnumRegistrationInfo Z_Registration_Info_UEnum_EMyEnum;

컬렉션 과정에서 MyEnum의 타입 정보는 FEnumRegistrationInfo에 채워집니다. 이 과정에서는 Enumerators[], FMetadatasPairParams[], 그리고 주요 데이터 구조체인 FEnumParams가 구성됩니다. 특히 FEnumParams는 패키지 함수 포인터를 받아 플래그를 설정하고, 수집된 정보를 조직하는 중심 역할을 합니다.

Enum 객체의 생성은 다음과 같이 이루어집니다.

REFLECTIONTEST_API UEnum* Z_Construct_UEnum_ReflectionTest_EMyEnum();

이 함수는 다시 UECodeGen_Private::ConstructUEnum을 호출합니다.

void ConstructUEnum(UEnum*& OutEnum, const FEnumParams& Params)
{
	UObject* (*OuterFunc)() = Params.OuterFunc;
	UObject* Outer = OuterFunc ? OuterFunc() : nullptr;
	if (OutEnum)
	{
		return;
	}
	UEnum* NewEnum = new (EC_InternalUseOnlyConstructor, Outer, UTF8_TO_TCHAR(Params.NameUTF8), Params.ObjectFlags)
	UEnum(FObjectInitializer());
	OutEnum = NewEnum;
	
	TArray<TPair<FName, int64>> EnumNames;
	EnumNames.Reserve(Params.NumEnumerators);
	for (const FEnumeratorParam* Enumerator = Params.EnumeratorParams, *EnumeratorEnd = Enumerator +
	Params.NumEnumerators; Enumerator != EnumeratorEnd; ++Enumerator)
	{
		EnumNames.Emplace(UTF8_TO_TCHAR(Enumerator->NameUTF8), Enumerator->Value);
	}
	const bool bAddMaxKeyIfMissing = true;
	NewEnum->SetEnums(EnumNames, (UEnum::ECppForm)Params.CppForm, Params.EnumFlags, bAddMaxKeyIfMissing);
	NewEnum->CppType = UTF8_TO_TCHAR(Params.CppTypeUTF8);
	if (Params.DisplayNameFunc)
	{
		NewEnum->SetEnumDisplayNameFn(Params.DisplayNameFunc);
	}
	#if WITH_METADATA
		AddMetaData(NewEnum, Params.MetaDataArray, Params.NumMetaData);
	#endif

UEnum의 주소 포인터가 전달되고, 함수 포인터들은 정적 EEnumParams 구조체에 제공됩니다. TRegistrationInfoFEnumRegistrationInfo를 사용한다고 표시되어 있습니다. 이 구조체는 내부 및 외부 싱글턴에 대한 포인터를 보관하는데, 여기서 '내부(inner)'는 자기 자신을, '외부(outer)'는 패키지를 의미합니다. 수집(컬렉션) 단계에서는 '외부'와 '내부'의 의미가 다르다는 점에 유의해야 합니다.

RegisterCompiledInfo 함수는 정적 객체의 파라미터를 전달받아 FRegisterCompiledInfo의 생성자에서 호출됩니다. 이때 C++ 퍼펙트 포워딩(perfect forwarding)이 사용되어, 템플릿에서 RValue와 LValue 문제를 우회합니다. 이 기법은 C++11부터 제공되며, 매우 특정한 상황에서 활용됩니다. 엔진 소스에서는 RegisterCompiledInfo 함수가 UClass, UStruct, UEnum 등 다양한 템플릿 버전으로 오버로드되어 있으며, 이를 위해 퍼펙트 포워딩이 사용된다는 점을 확인할 수 있습니다.

struct FRegisterCompiledInInfo
{
    template <typename ... Args>
    FRegisterCompiledInInfo(Args&& ... args)
    {
    RegisterCompiledInInfo(std::forward<Args>(args)...);
    }
};


RegisterCompiledInInfo(class UEnum* (*InOuterRegister)(), const TCHAR* InPackageName, const TCHAR* InName,
FEnumRegistrationInfo& InInfo, const FEnumReloadVersionInfo& InVersionInfo)


class UEnum *GetStaticEnum(class UEnum *(*InRegister)(), UObject* EnumOuter, const TCHAR* EnumName)
{
    UEnum *Result = (*InRegister)();
    NotifyRegistrationEvent(*EnumOuter->GetOutermost()->GetName(), EnumName, ENotifyRegistrationType::NRT_Enum,
    ENotifyRegistrationPhase::NRP_Finished, nullptr, false, Result);
    return Result;
}

GetStaticEnum 함수는 MyEnum의 스켈레톤 객체를 생성하는 역할을 합니다. 이 함수의 참조는 EnumInfo[]에 저장되며, 이후 이 정보는 정적 구조체인 FRegisterCompiledInInfo 타입에 전달됩니다.

profile
언리얼 엔진 주니어 개발자 입니다.

0개의 댓글