[Unreal Engine] Reflection System - Generation 3

Imeamangryang·2025년 6월 24일

Unreal Reflection System

목록 보기
4/10
post-thumbnail

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


Type System Code Generation

UStruct 해부

FMyStruct.generated.h

#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_59_GENERATED_BODY 
friend struct Z_Construct_UScriptStruct_FMyStruct_Statics; 
static class UScriptStruct* StaticStruct();
template<> REFLECTIONTEST_API UScriptStruct* StaticStruct<struct FMyStruct>();

마찬가지로, GENERATED_USTRUCT_BODY()도 우리가 정의한 매크로로 치환됩니다. 이 매크로는 내부적으로 StaticStruct 함수만 정의하며, FMyStruct는 UObject를 상속하지 않기 때문입니다.

FMyStruct.generated.cpp

static FStructRegistrationInfo Z_Registration_Info_UScriptStruct_MyStruct;
class UScriptStruct* FMyStruct::StaticStruct()
{
	if (!Z_Registration_Info_UScriptStruct_MyStruct.OuterSingleton)
	{
		Z_Registration_Info_UScriptStruct_MyStruct.OuterSingleton = GetStaticStruct(Z_Construct_UScriptStruct_FMyStruct,
		(UObject*)Z_Construct_UPackage__Script_ReflectionTest(), TEXT("MyStruct"));
	}
	return Z_Registration_Info_UScriptStruct_MyStruct.OuterSingleton;
}
template<> REFLECTIONTEST_API UScriptStruct* StaticStruct<FMyStruct>()
{
	return FMyStruct::StaticStruct();
}
struct Z_Construct_UScriptStruct_FMyStruct_Statics
{
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Struct_MetaDataParams[];
	#endif
	static void* NewStructOps();
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam NewProp_Score_MetaData[];
	#endif
	static const UECodeGen_Private::FFloatPropertyParams NewProp_Score;
	static const UECodeGen_Private::FPropertyParamsBase* const PropPointers[];
	static const UECodeGen_Private::FStructParams ReturnStructParams;
};
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam Z_Construct_UScriptStruct_FMyStruct_Statics::Struct_MetaDataParams[] = {
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
void* Z_Construct_UScriptStruct_FMyStruct_Statics::NewStructOps()
{
	return (UScriptStruct::ICppStructOps*)new UScriptStruct::TCppStructOps<FMyStruct>();
}
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam Z_Construct_UScriptStruct_FMyStruct_Statics::NewProp_Score_MetaData[] = {
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
const UECodeGen_Private::FFloatPropertyParams Z_Construct_UScriptStruct_FMyStruct_Statics::NewProp_Score = { "Score",
nullptr, (EPropertyFlags)0x0010000000000000, UECodeGen_Private::EPropertyGenFlags::Float,
RF_Public|RF_Transient|RF_MarkAsNative, 1, nullptr, nullptr, STRUCT_OFFSET(FMyStruct, Score),
METADATA_PARAMS(Z_Construct_UScriptStruct_FMyStruct_Statics::NewProp_Score_MetaData,
UE_ARRAY_COUNT(Z_Construct_UScriptStruct_FMyStruct_Statics::NewProp_Score_MetaData)) };

const UECodeGen_Private::FPropertyParamsBase* const Z_Construct_UScriptStruct_FMyStruct_Statics::PropPointers[] = {
	
	(const UECodeGen_Private::FPropertyParamsBase*)&Z_Construct_UScriptStruct_FMyStruct_Statics::NewProp_Score,
};

const UECodeGen_Private::FStructParams Z_Construct_UScriptStruct_FMyStruct_Statics::ReturnStructParams = {
	(UObject* (*)())Z_Construct_UPackage__Script_ReflectionTest,
	nullptr,
	&NewStructOps,
	"MyStruct",
	sizeof(FMyStruct),
	alignof(FMyStruct),
	Z_Construct_UScriptStruct_FMyStruct_Statics::PropPointers,
	UE_ARRAY_COUNT(Z_Construct_UScriptStruct_FMyStruct_Statics::PropPointers),
	RF_Public|RF_Transient|RF_MarkAsNative,
	EStructFlags(0x00000201),
	METADATA_PARAMS(Z_Construct_UScriptStruct_FMyStruct_Statics::Struct_MetaDataParams,
	UE_ARRAY_COUNT(Z_Construct_UScriptStruct_FMyStruct_Statics::Struct_MetaDataParams))
};

UScriptStruct* Z_Construct_UScriptStruct_FMyStruct()
{
	if (!Z_Registration_Info_UScriptStruct_MyStruct.InnerSingleton)
	{
		UECodeGen_Private::ConstructUScriptStruct(Z_Registration_Info_UScriptStruct_MyStruct.InnerSingleton,
		Z_Construct_UScriptStruct_FMyStruct_Statics::ReturnStructParams);
	}
	return Z_Registration_Info_UScriptStruct_MyStruct.InnerSingleton;
}

FMyStruct의 생성 경로는 UClass와 유사하며, FMyStruct::StaticStruct 내부에는 Z_Construct_UScriptStruct_FMyStruct와 비교해 추가적인 요소가 없습니다. 주요 차이점은 등록 과정에 있습니다. RegisterCompiledInInfo에서 UStruct를 등록할 때, DeferCppStructOps 호출이 이루어집니다. 이 호출은 프로그램 시작 시점에 구조체의 C++ 정보(크기, 메모리, 정렬 등)를 등록합니다. 이는 UClass의 경우 IMPLEMENT_CLASS 매크로가 삽입하는 AutoInitialize 함수와 동일한 역할을 수행합니다.


UInterface 해부

언리얼 엔진에서 UInterface는 오직 메서드만을 포함하는 순수 인터페이스를 의미합니다. 이는 전통적인 C++의 추상 클래스보다 더 순수한 형태로 간주되며, 내부 요소가 해당 매크로로 명시되어 있을 때 그렇습니다. 언리얼 엔진은 진짜 C++ 필드와 함수 자체를 직접 처리하지 않기 때문에, 필드와 함수의 관리는 반드시 매크로를 통해 이루어집니다. UInterface를 사용하면 언리얼 엔진 프레임워크 내에서 인터페이스 정의를 명확하게 분리할 수 있습니다.

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

GENERATED_UINTERFACE_BODYGENERATED_IINTERFACE_BODY는 모두 GENERATED_BODY로 대체할 수 있으며, 이 경우 기본 생성자인 UMyInterface(const FObjectInitializer& ObjectInitializer)가 자동으로 구현됩니다. 하지만 GENERATED_IINTERFACE_BODY를 대체하더라도 실제로는 동일한 효과를 가지며, 인터페이스는 생성자가 필요하지 않기 때문에 두 매크로는 이 맥락에서 사실상 동일하게 동작합니다.

UMyInterface.generated.h

#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_STANDARD_CONSTRUCTORS
	/** Standard constructor, called after all reflected properties have been initialized */
	NO_API UMyInterface(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
	DEFINE_ABSTRACT_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyInterface)
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyInterface);
	DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyInterface);
private:
	/** Private move- and copy-constructors, should never be used */
	NO_API UMyInterface(UMyInterface&&);
	NO_API UMyInterface(const UMyInterface&);
public:
	NO_API virtual ~UMyInterface();
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_ENHANCED_CONSTRUCTORS
	/** Standard constructor, called after all reflected properties have been initialized */
	NO_API UMyInterface(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
private:
	/** Private move- and copy-constructors, should never be used */
	NO_API UMyInterface(UMyInterface&&);
	NO_API UMyInterface(const UMyInterface&);
public:
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyInterface);
	DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyInterface);
	DEFINE_ABSTRACT_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyInterface)
	NO_API virtual ~UMyInterface();
#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_GENERATED_UINTERFACE_BODY()
private:
	static void StaticRegisterNativesUMyInterface();
	friend struct Z_Construct_UClass_UMyInterface_Statics;
public:
	DECLARE_CLASS(UMyInterface, UInterface, COMPILED_IN_FLAGS(CLASS_Abstract | CLASS_Interface), CASTCLASS_None,
	TEXT("/Script/ReflectionTest"), NO_API)
	DECLARE_SERIALIZER(UMyInterface)

#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_GENERATED_BODY_LEGACY
	PRAGMA_DISABLE_DEPRECATION_WARNINGS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_GENERATED_UINTERFACE_BODY()
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_STANDARD_CONSTRUCTORS
	PRAGMA_ENABLE_DEPRECATION_WARNINGS
	#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_GENERATED_BODY
	PRAGMA_DISABLE_DEPRECATION_WARNINGS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_GENERATED_UINTERFACE_BODY()
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_ENHANCED_CONSTRUCTORS
private:
	PRAGMA_ENABLE_DEPRECATION_WARNINGS
	#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_INCLASS_IINTERFACE_NO_PURE_DECLS
protected:
virtual ~IMyInterface() {}
public:
	typedef UMyInterface UClassType;
	typedef IMyInterface ThisClass;
	static void Execute_BPFunc(const UObject* O);
	virtual UObject* _getUObject() const { return nullptr; }
	#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_INCLASS_IINTERFACE
protected:
	virtual ~IMyInterface() {}
public:
	typedef UMyInterface UClassType;
	typedef IMyInterface ThisClass;
static void Execute_BPFunc(const UObject* O);
	virtual UObject* _getUObject() const { return nullptr; }
	#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_13_PROLOG
	#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_21_GENERATED_BODY_LEGACY
PRAGMA_DISABLE_DEPRECATION_WARNINGS
public:
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_SPARSE_DATA
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_RPC_WRAPPERS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_ACCESSORS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_CALLBACK_WRAPPERS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_INCLASS_IINTERFACE
public:
PRAGMA_ENABLE_DEPRECATION_WARNINGS
	#define FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_21_GENERATED_BODY
PRAGMA_DISABLE_DEPRECATION_WARNINGS
public:
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_SPARSE_DATA
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_RPC_WRAPPERS_NO_PURE_DECLS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_ACCESSORS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_CALLBACK_WRAPPERS
	FID_Users_StaticJPL_Documents_Unreal_Projects_ReflectionTest_Source_ReflectionTest_MyClass_h_16_INCLASS_IINTERFACE_NO_PURE_DECLS
private:
PRAGMA_ENABLE_DEPRECATION_WARNINGS
template<> REFLECTIONTEST_API UClass* StaticClass<class UMyInterface>();

인터페이스 정의에는 두 개의 클래스가 필요하며, 이로 인해 생성되는 정보가 약간 복잡해집니다. 여기서 UInterface가 도입됩니다. 하지만 실제로 UMyInterface 클래스는 단순히 IMyInterface를 상속받아 인터페이스 타입을 전달하는 역할만 하며, 인터페이스임을 구분하는 용도로 사용됩니다. 엔진 주석에 따르면, UClassFImplementInterface를 통해 해당 클래스가 구현한 모든 인터페이스 정보를 보관합니다. 또한, VTable의 오프셋을 활용하는 특이한 방식으로 포인터 프로퍼티를 포함하고 있는데, 만약 인터페이스가 네이티브가 아니라면 이 프로퍼티는 null로 설정됩니다.

class IMyInterface
{
protected:
    virtual ~IMyInterface() {}
public:
    typedef UMyInterface UClassType;
    typedef IMyInterface ThisClass;
    static void Execute_BPFunc(const UObject* O);
public:
    UFUNCTION(BlueprintImplementableEvent)
    void BPFunc() const;
};

코드 상단에서 UMyInterface의 생성 과정은 UMyInterfaceUObject를 상속하고 UObject 시스템의 일부이기 때문에 생성 규칙을 따릅니다. UClass의 생성 코드와 유사하게, UInterface 코드 생성 시에도 COMPILED_IN_FLAGS(CLASS_Abstract | CLASS_Interface)와 같은 플래그가 설정됩니다.

UMyInterface.generated.cpp

void EmptyLinkFunctionForGeneratedCodeMyClass() {}
...
REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyInterface();
REFLECTIONTEST_API UClass* Z_Construct_UClass_UMyInterface_NoRegister();
...
void IMyInterface::BPFunc() const // To pass compilation and include error checking
{
	check(0 && "Do not directly call Event functions in Interfaces. Call Execute_BPFunc instead.");
}
void UMyInterface::StaticRegisterNativesUMyInterface()
{
}
struct Z_Construct_UFunction_UMyInterface_BPFunc_Statics
{
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];
	#endif
	static const UECodeGen_Private::FFunctionParams FuncParams;
};
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam 
Z_Construct_UFunction_UMyInterface_BPFunc_Statics::Function_MetaDataParams[] =
{
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
const UECodeGen_Private::FFunctionParams
 Z_Construct_UFunction_UMyInterface_BPFunc_Statics::FuncParams = { (UObject*(*)
	())Z_Construct_UClass_UMyInterface, nullptr, "BPFunc", nullptr, nullptr, 0, nullptr, 0,
	RF_Public|RF_Transient|RF_MarkAsNative, (EFunctionFlags)0x48020800, 0, 0,
	METADATA_PARAMS(Z_Construct_UFunction_UMyInterface_BPFunc_Statics::Function_MetaDataParams,
	UE_ARRAY_COUNT(Z_Construct_UFunction_UMyInterface_BPFunc_Statics::Function_MetaDataParams)) };

UFunction* Z_Construct_UFunction_UMyInterface_BPFunc()
{
	static UFunction* ReturnFunction = nullptr;
	if (!ReturnFunction)
	{
		UECodeGen_Private::ConstructUFunction(&ReturnFunction,
		Z_Construct_UFunction_UMyInterface_BPFunc_Statics::FuncParams);
	}
	return ReturnFunction;
}
	// Same like UClass
	IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(UMyInterface);
	UClass* Z_Construct_UClass_UMyInterface_NoRegister()
	{
	return UMyInterface::StaticClass();
	}
struct Z_Construct_UClass_UMyInterface_Statics
{
	static UObject* (*const DependentSingletons[])();
	static const FClassFunctionLinkInfo FuncInfo[];
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Class_MetaDataParams[];
	#endif
	static const FCppClassTypeInfoStatic StaticCppClassTypeInfo;
	static const UECodeGen_Private::FClassParams ClassParams;
};
UObject* (*const Z_Construct_UClass_UMyInterface_Statics::DependentSingletons[])() = {
	(UObject* (*)())Z_Construct_UClass_UInterface,
	(UObject* (*)())Z_Construct_UPackage__Script_ReflectionTest,
};
const FClassFunctionLinkInfo Z_Construct_UClass_UMyInterface_Statics::FuncInfo[] = {
	{ &Z_Construct_UFunction_UMyInterface_BPFunc, "BPFunc" }, // 2802701767
};
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam Z_Construct_UClass_UMyInterface_Statics::Class_MetaDataParams[] 
= {
	{ "BlueprintType", "true" },
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
const FCppClassTypeInfoStatic Z_Construct_UClass_UMyInterface_Statics::StaticCppClassTypeInfo = {
	TCppClassTypeTraits<IMyInterface>::IsAbstract,
};
const UECodeGen_Private::FClassParams Z_Construct_UClass_UMyInterface_Statics::ClassParams = {
	&UMyInterface::StaticClass,
	nullptr,
	&StaticCppClassTypeInfo,
	DependentSingletons,
	FuncInfo,
	nullptr,
	nullptr,
	UE_ARRAY_COUNT(DependentSingletons),
	UE_ARRAY_COUNT(FuncInfo),
	0,
	0,
	0x001040A1u,
	METADATA_PARAMS(Z_Construct_UClass_UMyInterface_Statics::Class_MetaDataParams,
	UE_ARRAY_COUNT(Z_Construct_UClass_UMyInterface_Statics::Class_MetaDataParams))
};

UClass* Z_Construct_UClass_UMyInterface()
{
	if (!Z_Registration_Info_UClass_UMyInterface.OuterSingleton)
	{
	UECodeGen_Private::ConstructUClass(Z_Registration_Info_UClass_UMyInterface.OuterSingleton,
	Z_Construct_UClass_UMyInterface_Statics::ClassParams);
	}
	return Z_Registration_Info_UClass_UMyInterface.OuterSingleton;
}
template<> REFLECTIONTEST_API UClass* StaticClass<UMyInterface>()
{
	return UMyInterface::StaticClass();
}
DEFINE_VTABLE_PTR_HELPER_CTOR(UMyInterface);
UMyInterface::~UMyInterface() {}
static FName NAME_UMyInterface_BPFunc = FName(TEXT("BPFunc")); // Definition of the name
void IMyInterface::Execute_BPFunc(const UObject* O)
{
	check(O != NULL);
	check(O->GetClass()->ImplementsInterface(UMyInterface::StaticClass()));
	UFunction* const Func = O->FindFunction(NAME_UMyInterface_BPFunc);
	if (Func)
	{
	const_cast<UObject*>(O)->ProcessEvent(Func, NULL);
	}
}
...
Delayed Registrations at the bottom of CPP file

UMyInterfaceUClass와 유사한 구조를 가지지만, 인터페이스 클래스 타입에 대한 함수 추가, 생성, 정의 등에서 추가적인 처리가 필요합니다.


UClass의 필드 및 함수 생성

UMyClass 내부에 몇 가지 필드와 함수를 추가했지만, 이 코드가 어떻게 생성되는지에 대해서는 지금까지 다루지 않았습니다. 이번 섹션에서는 해당 코드의 생성 과정을 살펴봅니다.

UPROPERTY()
int Health;
UFUNCTION(BlueprintCallable, Category = "Hello")
void CallableFunc();
UFUNCTION(BlueprintNativeEvent, Category = "Hello")
void NativeFunc();
UFUNCTION(BlueprintImplementableEvent, Category = "Hello")
void ImplementableFunc();

UMyClass.generated.h

#define FID_FilePath_ReflectionTest_Source_ReflectionTest_MyClass_h_30_RPC_WRAPPERS
    virtual void NativeFunc_Implementation();
    DECLARE_FUNCTION(execNativeFunc);
    DECLARE_FUNCTION(execCallableFunc);
#define FID_FilePath_Source_ReflectionTest_MyClass_h_30_RPC_WRAPPERS_NO_PURE_DECLS

UMyClass.generate.cpp

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;
}

ImplementableFunc는 C++에서 구현되므로, UE가 해당 함수의 본문을 자동으로 생성합니다. 따라서 별도로 함수 본문을 작성할 필요가 없습니다. 반면, NativeFuncCallableFunc는 블루프린트에서 정의되기 때문에, 이 함수들을 블루프린트에서 호출할 수 있도록 별도의 exec 접두사가 붙은 함수가 생성되어 블루프린트 가상 머신에서 호출이 가능해집니다.

ExecCallableFunc 확장 예시

void execCallableFunc(FFrame& Stack, void* const Z_Param__Result)
// 블루프린트 가상 머신에서 사용하는 함수 인터페이스
{
	Stack.Code += !!Stack.Code; // 코드 포인터를 증가시킴 (널이 아니면)
	{
		FBlueprintEventTimer::FScopedNativeTimer ScopedNativeCallTimer; // 블루프린트 타이밍 통계용
		this->CallableFunc(); // 실제 구현 함수 호출
	}
}

함수 시그니처에 따라 파라미터가 달라질 수 있습니다. 위 함수들은 모두 UMyClass 내부에 정의됩니다.

ImplementableFunc()

static FName NAME_UMyClass_ImplementableFunc = FName(TEXT("ImplementableFunc"));
void UMyClass::ImplementableFunc()
{
	// Implementation on the C++ side
	ProcessEvent(FindFunctionChecked(NAME_UMyClass_ImplementableFunc),NULL);
}
// Define the Static Collection Data structures for construction and registration
struct Z_Construct_UFunction_UMyClass_ImplementableFunc_Statics
{
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];
	#endif
	static const UECodeGen_Private::FFunctionParams FuncParams;
};
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam
Z_Construct_UFunction_UMyClass_ImplementableFunc_Statics::Function_MetaDataParams[] = {
	{ "Category", "Hello" },
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
// Fill the Function parameters and data to pass to construct UECodeGen_Private
const UECodeGen_Private::FFunctionParams 
Z_Construct_UFunction_UMyClass_ImplementableFunc_Statics::FuncParams =
{ (UObject*(*)())Z_Construct_UClass_UMyClass, nullptr, 
"ImplementableFunc", nullptr, nullptr, 0, nullptr, 0,
RF_Public|RF_Transient|RF_MarkAsNative, 
(EFunctionFlags)0x08020800, 0, 0,
METADATA_PARAMS(Z_Construct_UFunction_UMyClass_ImplementableFunc_Statics::Function_MetaDataParams,
UE_ARRAY_COUNT(Z_Construct_UFunction_UMyClass_ImplementableFunc_Statics::Function_MetaDataParams))};


// Calling Construction UFunction to build the UFunction Object
UFunction* Z_Construct_UFunction_UMyClass_ImplementableFunc()
{
    static UFunction* ReturnFunction = nullptr;
    if (!ReturnFunction)
    {
        UECodeGen_Private::ConstructUFunction(&ReturnFunction,
        Z_Construct_UFunction_UMyClass_ImplementableFunc_Statics::FuncParams);
    }
    return ReturnFunction;
}

CallableFunc()

struct Z_Construct_UFunction_UMyClass_CallableFunc_Statics
{
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];
	#endif
	static const UECodeGen_Private::FFunctionParams FuncParams;
};
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam 
Z_Construct_UFunction_UMyClass_CallableFunc_Statics::Function_MetaDataParams[] = {
	{ "Category", "Hello" },
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
const UECodeGen_Private::FFunctionParams 
Z_Construct_UFunction_UMyClass_CallableFunc_Statics::FuncParams = { (UObject*(*)
())Z_Construct_UClass_UMyClass, nullptr, "CallableFunc", nullptr, nullptr, 0, nullptr, 0,
RF_Public|RF_Transient|RF_MarkAsNative, (EFunctionFlags)0x04020401, 0, 0,
METADATA_PARAMS(Z_Construct_UFunction_UMyClass_CallableFunc_Statics::Function_MetaDataParams,
UE_ARRAY_COUNT(Z_Construct_UFunction_UMyClass_CallableFunc_Statics::Function_MetaDataParams)) };

UFunction* Z_Construct_UFunction_UMyClass_CallableFunc()
{
	static UFunction* ReturnFunction = nullptr;
	if (!ReturnFunction)
	{
		UECodeGen_Private::ConstructUFunction(&ReturnFunction,
		Z_Construct_UFunction_UMyClass_CallableFunc_Statics::FuncParams);
	}
	return ReturnFunction;
}

NativeFunc()

struct Z_Construct_UFunction_UMyClass_NativeFunc_Statics
{
	#if WITH_METADATA
	static const UECodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];
	#endif
	static const UECodeGen_Private::FFunctionParams FuncParams;
};
#if WITH_METADATA
const UECodeGen_Private::FMetaDataPairParam 
Z_Construct_UFunction_UMyClass_NativeFunc_Statics::Function_MetaDataParams[] =
{
	{ "Category", "Hello" },
	{ "ModuleRelativePath", "MyClass.h" },
};
#endif
const UECodeGen_Private::FFunctionParams Z_Construct_UFunction_UMyClass_NativeFunc_Statics::FuncParams = { (UObject*(*)
())Z_Construct_UClass_UMyClass, nullptr, "NativeFunc", nullptr, nullptr, 0, nullptr, 0,
RF_Public|RF_Transient|RF_MarkAsNative, (EFunctionFlags)0x08020C00, 0, 0,
METADATA_PARAMS(Z_Construct_UFunction_UMyClass_NativeFunc_Statics::Function_MetaDataParams,
UE_ARRAY_COUNT(Z_Construct_UFunction_UMyClass_NativeFunc_Statics::Function_MetaDataParams)) };
UFunction* Z_Construct_UFunction_UMyClass_NativeFunc()
{
	static UFunction* ReturnFunction = nullptr;
	if (!ReturnFunction)
	{
		UECodeGen_Private::ConstructUFunction(&ReturnFunction,
		Z_Construct_UFunction_UMyClass_NativeFunc_Statics::FuncParams);
	}
	return ReturnFunction;
}

생성 흐름

위와 동일한 패턴으로, 함수 선언부와 정적 데이터 구조체들이 함께 수집되고, 이후 해당 객체 생성을 담당하는 UECodeGen_PrivateConstruct 네임스페이스 함수가 호출됩니다. 이 함수들은 모두 UMyClass 내부에 정의되어 있으며, 이 과정에서 일괄적으로 수집됩니다.

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
};

C++에서 CallableFunc의 구현은 블루프린트가 해당 메서드를 직접 호출할 수 있도록 하며, 생성된 코드는 단순히 이에 대응하는 UFunction 포인터 객체만을 생성합니다. 반면, NativeFuncImplementableFunc의 경우 C++에서 별도의 구현을 작성할 필요가 없습니다. 하지만 컴파일을 원활하게 하고 C++ 코드에서 직접 호출이 가능하도록 하기 위해, 코드 생성 시 기본 구현이 자동으로 생성됩니다.

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));

Snippet of the ConstructUFunction

FORCEINLINE void ConstructUFunctionInternal(UFunction*& OutFunction, 
const FFunctionParams& Params,
UFunction** SingletonPtr)
{
	UObject* (*OuterFunc)() = Params.OuterFunc;
	UFunction* (*SuperFunc)() = Params.SuperFunc;
	UObject* Outer = OuterFunc ? OuterFunc() : nullptr;
	UFunction* Super = SuperFunc ? SuperFunc() : nullptr;
	if (OutFunction)
	{
		return;
	}
	FName FuncName(UTF8_TO_TCHAR(Params.NameUTF8));
	#if WITH_LIVE_CODING
	// When a package is patched, it might reference a function in a class. When this happens, the existing UFunction
	// object gets reused but the UField's Next pointer gets nulled out. This ends up terminating the function list
	// for the class. To work around this issue, cache the next pointer and then restore it after the new instance
	// is created. Only do this if we reuse the current instance.
	UField* PrevFunctionNextField = nullptr;
	UFunction* PrevFunction = nullptr;
	if (UObject* PrevObject = StaticFindObjectFastInternal( /*Class=*/ nullptr, Outer, FuncName, true))
	{
		PrevFunction = Cast<UFunction>(PrevObject);
		if (PrevFunction != nullptr)
		{
			PrevFunctionNextField = PrevFunction->Next;
		}
	}
	#endif
	UFunction* NewFunction;
	if (Params.FunctionFlags & FUNC_Delegate)
	{
		if (Params.OwningClassName == nullptr)
		{
			NewFunction = new (EC_InternalUseOnlyConstructor, Outer, FuncName, Params.ObjectFlags) UDelegateFunction(
			FObjectInitializer(),
			Super,
			Params.FunctionFlags,
			Params.StructureSize
			);
		}
		else
		{
			USparseDelegateFunction* NewSparseFunction = new (EC_InternalUseOnlyConstructor, Outer, FuncName,
			Params.ObjectFlags) USparseDelegateFunction(
			FObjectInitializer(),
			Super,
			Params.FunctionFlags,
			Params.StructureSize
			);
			NewSparseFunction->OwningClassName = FName(Params.OwningClassName);
			NewSparseFunction->DelegateName = FName(Params.DelegateName);
			NewFunction = NewSparseFunction;
		}
	}
	else
	{
		NewFunction = new (EC_InternalUseOnlyConstructor, Outer, FuncName, Params.ObjectFlags) UFunction(
		FObjectInitializer(),
		Super,
		Params.FunctionFlags,
		Params.StructureSize
		);
	}
	OutFunction = NewFunction;
	#if WITH_LIVE_CODING
	NewFunction->SingletonPtr = SingletonPtr;
	if (NewFunction == PrevFunction)
	{
		NewFunction->Next = PrevFunctionNextField;
	}
	#endif
	#if WITH_METADATA
	AddMetaData(NewFunction, Params.MetaDataArray, Params.NumMetaData);
	#endif
	NewFunction->RPCId = Params.RPCId;
	NewFunction->RPCResponseId = Params.RPCResponseId;
	ConstructFProperties(NewFunction, Params.PropertyArray, Params.NumProperties);
	NewFunction->Bind();
	NewFunction->StaticLink();
}

UFunction*의 스켈레톤을 구성하는 첫 단계는 UMyClass가 자신을 생성한 후, CreateLinkAndAddChildFunctionsToMap을 호출하는 것입니다.

void UClass::CreateLinkAndAddChildFunctionsToMap
(const FClassFunctionLinkInfo* Functions, uint32 NumFunctions)
{
    for (; NumFunctions; --NumFunctions, ++Functions)
    {
       const char* FuncNameUTF8 = Functions->FuncNameUTF8;
       UFunction* Func = Functions->CreateFuncPtr();
       Func->Next = Children;
       Children = Func;
       AddFunctionToFunctionMap(Func, FName(UTF8_TO_TCHAR(FuncNameUTF8)));
    }
}

TMap은 함수 이름(FName)과 해당 함수의 UFunction 포인터를 매핑하여 저장합니다. UObject 포인터는 해당 함수가 소속된 클래스를 참조하며, Super 함수는 UFunctionSuperStruct를 가리켜 슈퍼클래스 또는 인터페이스 역할을 합니다. 엔진은 자식 클래스에서 부모 함수 정보를 런타임에 캐싱하여 최적화하며, 이 캐싱 메커니즘은 블루프린트 함수 호출 시 FindFunctionByName에서 활용됩니다.

Summary

정리하면, 코드 생성 과정에서 UEnum, UMyStruct, UMyInterface, UFunction 등 다양한 타입에 대한 함수와 정적 데이터 구조가 생성됩니다. 이들은 UMyClass의 프로퍼티와 메서드로 정의 및 추가됩니다. Enum의 경우 이름과 값이 기록되고, Struct는 각 프로퍼티의 이름과 바이트 오프셋이 저장됩니다. 각 함수 및 래퍼 함수에 대한 함수 포인터와 기본 생성자 등도 정의됩니다.

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

0개의 댓글