从Super类型看UHT功能

问题

在UE的代码中,经常可以看到对于父类类型的引用,因为很多情况下都要先调用父类的同名函数。例如随便看下UE的部分代码,在实现自己序列化函数的时候先调用基类的序列化函数。

但是,尽管Super是一个非常顺数的功能(在行为树库behaviac数中也有super定义),但是C++并没有实现这种功能。这个其实也很容易理解:在多重继承中,super不能位于确定一个基类。这也意味着类似于super这种基类的实现要通过某种约定来消除歧义性。

void UAIGraph::Serialize(FArchive& Ar)
{
	// Overridden to flags up errors in the behavior tree while cooking.
	Super::Serialize(Ar);

	if (Ar.IsSaving() || Ar.IsCooking())
	{
		// Logging of errors happens in UpdateDeprecatedClasses
		UpdateDeprecatedClasses();
	}
}

从UE的代码可以看到,Super的定义位于ObjectMacros.h文件,每个类的基类又是DECLARE_CLASS宏传入的参数。因为DECLARE_CLASS的调用是UHT生成的,所以还要再从UHT中查看宏调用时传入的TSuperClass是什么。

///@file:Engine\Source\Runtime\CoreUObject\Public\UObject\ObjectMacros.h
/*-----------------------------------------------------------------------------
	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.*/ \
	enum {StaticClassFlags=TStaticFlags}; \
	/** Typedef for the base class ({{ typedef-type }}) */ \
	typedef TSuperClass Super;\
	/** Typedef for {{ typedef-type }}. */ \
	typedef TClass ThisClass;\

Super处理

Super解析

UClass类的解析位于FHeaderPreParser::ParseClassDeclaration函数,函数解析的时候只是简单的把第一个基类作为类的BaseClassNameToken。

void FHeaderPreParser::ParseClassDeclaration(const TCHAR* Filename, const TCHAR* InputText, int32 InLineNumber, const TCHAR* StartingMatchID, FName& out_StrippedClassName, FString& out_ClassName, FString& out_BaseClassName, TArray<FHeaderProvider>& out_RequiredIncludes, const TArray<FSimplifiedParsingClassInfo>& ParsedClassArray)
{
///...
	// Skip optional final keyword
	MatchIdentifier(TEXT("final"), ESearchCase::CaseSensitive);

	// Handle inheritance
	if (MatchSymbol(TEXT(':')))
	{
		// Require 'public'
		RequireIdentifier(TEXT("public"), ESearchCase::CaseSensitive, ErrorMsg);

		// Inherits from something
		FToken BaseClassNameToken;
		if (!GetIdentifier(BaseClassNameToken, true))
		{
			FError::Throwf(TEXT("Expected a base class name"));
		}

		out_BaseClassName = BaseClassNameToken.Identifier;

		int32 InputLineLocal = InputLine;
		auto AddDependencyIfNeeded = [Filename, InputLineLocal, &ParsedClassArray, &out_RequiredIncludes, &ClassNameWithoutPrefixStr](const FString& DependencyClassName)
		{
			if (!Algo::FindBy(ParsedClassArray, DependencyClassName, &FSimplifiedParsingClassInfo::GetClassName))
			{
				FString DependencyClassNameWithoutPrefixStr = GetClassNameWithPrefixRemoved(DependencyClassName);

				if (ClassNameWithoutPrefixStr == DependencyClassNameWithoutPrefixStr)
				{
					FFileLineException::Throwf(Filename, InputLineLocal, TEXT("A class cannot inherit itself or a type with the same name but a different prefix"));
				}

				FString StrippedDependencyName = DependencyClassName.Mid(1);

				// Only add a stripped dependency if the stripped name differs from the stripped class name
				// otherwise it's probably a class with a different prefix.
				if (StrippedDependencyName != ClassNameWithoutPrefixStr)
				{
					out_RequiredIncludes.Add(FHeaderProvider(EHeaderProviderSourceType::ClassName, MoveTemp(StrippedDependencyName)));
				}
			}
		};

		AddDependencyIfNeeded(out_BaseClassName);

		// Get additional inheritance links and rack them up as dependencies if they're UObject derived
		while (MatchSymbol(TEXT(',')))
		{
			// Require 'public'
			RequireIdentifier(TEXT("public"), ESearchCase::CaseSensitive, ErrorMsg);

			FToken InterfaceClassNameToken;
			if (!GetIdentifier(InterfaceClassNameToken, true))
			{
				FFileLineException::Throwf(Filename, InputLine, TEXT("Expected an interface class name"));
			}

			AddDependencyIfNeeded(FString(InterfaceClassNameToken.Identifier));
		}
	}
///...
}

Super记录

解析结果放入数组中,可以看到,数组中存储的是类名和第一个基类的名字。注意:在FSimplifiedParsingClassInfo类的构造函数中传入的基类BaseClassName就是第一个类的名字。

// Performs a preliminary parse of the text in the specified buffer, pulling out useful information for the header generation process
void FHeaderParser::SimplifiedClassParse(const TCHAR* Filename, const TCHAR* InBuffer, TArray<FSimplifiedParsingClassInfo>& OutParsedClassArray, TArray<FHeaderProvider>& DependentOn, FStringOutputDevice& ClassHeaderTextStrippedOfCppText)
{
///...
					FName StrippedInterfaceName;
					Parser.ParseClassDeclaration(Filename, StartOfLine + (UInterfaceMacroDecl - Str), CurrentLine, TEXT("UINTERFACE"), /*out*/ StrippedInterfaceName, /*out*/ ClassName, /*out*/ BaseClassName, /*out*/ DependentOn, OutParsedClassArray);
					OutParsedClassArray.Add(FSimplifiedParsingClassInfo(MoveTemp(ClassName), MoveTemp(BaseClassName), CurrentLine, true));
					if (!bFoundExportedClasses)
					{
						if (FClassDeclarationMetaData* Found = GClassDeclarations.Find(StrippedInterfaceName))
						{
							bFoundExportedClasses = !(Found->ClassFlags & CLASS_NoExport);
						}
					}
///...
}

Super设置

在文件解析完成之后,通过名字查找找到每个UClass对象的基类,然后通过UClass::SetSuperStruct()函数设置/修改UClass的基类。


/**
 * Tries to resolve super classes for classes defined in the given
 * module.
 *
 * @param Package Modules package.
 */
void ResolveSuperClasses(UPackage* Package)
{
	TArray<UObject*> Objects;
	GetObjectsWithPackage(Package, Objects);

	for (UObject* Object : Objects)
	{
		if (!Object->IsA<UClass>() || Object->HasAnyFlags(RF_ClassDefaultObject))
		{
			continue;
		}

		UClass* DefinedClass = Cast<UClass>(Object);

		if (DefinedClass->HasAnyClassFlags(CLASS_Intrinsic | CLASS_NoExport))
		{
			continue;
		}

		const FSimplifiedParsingClassInfo& ParsingInfo = GTypeDefinitionInfoMap[DefinedClass]->GetUnrealSourceFile().GetDefinedClassParsingInfo(DefinedClass);

		const FString& BaseClassName         = ParsingInfo.GetBaseClassName();
		const FString& BaseClassNameStripped = GetClassNameWithPrefixRemoved(BaseClassName);

		if (!BaseClassNameStripped.IsEmpty() && !DefinedClass->GetSuperClass())
		{
			UClass* FoundBaseClass = FindObject<UClass>(Package, *BaseClassNameStripped);

			if (FoundBaseClass == nullptr)
			{
				FoundBaseClass = FindObject<UClass>(ANY_PACKAGE, *BaseClassNameStripped);
			}

			if (FoundBaseClass == nullptr)
			{
				// Don't know its parent class. Raise error.
				FError::Throwf(TEXT("Couldn't find parent type for '%s' named '%s' in current module (Package: %s) or any other module parsed so far."), *DefinedClass->GetName(), *BaseClassName, *GetNameSafe(Package));
			}

			DefinedClass->SetSuperStruct(FoundBaseClass);
			DefinedClass->ClassCastFlags |= FoundBaseClass->ClassCastFlags;
		}
	}
}

Super导出

从生成的UClass对象中获得一个对象的BaseClass,并把该对象的名字作为基类。

void FNativeClassHeaderGenerator::ExportClassFromSourceFileInner(
	FOutputDevice&           OutGeneratedHeaderText,
	FOutputDevice&           OutCpp,
	FOutputDevice&           OutDeclarations,
	FReferenceGatherers&     OutReferenceGatherers,
	FClass*                  Class,
	const FUnrealSourceFile& SourceFile,
	EExportClassOutFlags&    OutFlags
) const
{
///...
	FClass* SuperClass = Class->GetSuperClass();

	// the name for the C++ version of the UClass
	const FString ClassCPPName = FNameLookupCPP::GetNameCPP(Class);
	const FString SuperClassCPPName = (SuperClass ? FNameLookupCPP::GetNameCPP(SuperClass) : TEXT("None"));
	///...
		Boilerplate.Logf(TEXT("\tDECLARE_CLASS(%s, %s, COMPILED_IN_FLAGS(%s%s), %s, TEXT(\"%s\"), %s_API)\r\n"),
			*ClassCPPName,
			*SuperClassCPPName,
			Class->HasAnyClassFlags(CLASS_Abstract) ? TEXT("CLASS_Abstract") : TEXT("0"),
			*GetClassFlagExportText(Class),
			bCastedClass ? *FString::Printf(TEXT("CASTCLASS_%s"), *ClassCPPName) : TEXT("CASTCLASS_None"),
			*FClass::GetTypePackageName(Class),
			*APIArg);
///...
}

同header多UClass

UHT还有一个神奇的功能:同一个头文件可以包含多个UClass的声明,并且每个UClass声明中都包含了相同的

GENERATED_UCLASS_BODY

宏,这些宏都没有参数,那么在同一个文件中有多个UClass的时候同名的宏如何各自展开呢?

GENERATED_UCLASS_BODY扫描

在UHT中,对于"GENERATED_UCLASS_BODY"字符串的扫描和对"UClass"的扫描处理是同一级别的,也是关键的、需要特殊关注的定界符。

其中的代码逻辑的主要功能就是记录该宏所在的行号(及所在的UClass类)

ClassData->SetGeneratedBodyLine(InputLine);
//
// Compile a declaration in Token. Returns 1 if compiled, 0 if not.
//
bool FHeaderParser::CompileDeclaration(FClasses& AllClasses, TArray<UDelegateFunction*>& DelegatesToFixup, FToken& Token)
{
///...
if (Token.Matches(TEXT("GENERATED_UINTERFACE_BODY"), ESearchCase::CaseSensitive) || (Token.Matches(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive) && TopNest->NestType == ENestType::Interface))

	if (Token.Matches(TEXT("GENERATED_UCLASS_BODY"), ESearchCase::CaseSensitive) || (Token.Matches(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive) && TopNest->NestType == ENestType::Class))
	{
		if (TopNest->NestType != ENestType::Class)
		{
			FError::Throwf(TEXT("%s must occur inside the class definition"), Token.Identifier);
		}

		FClassMetaData* ClassData = GetCurrentClassData();

		if (Token.Matches(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive))
		{
			if (!ClassDefinitionRanges.Contains(GetCurrentClass()))
			{
				ClassDefinitionRanges.Add(GetCurrentClass(), ClassDefinitionRange());
			}

			ClassDefinitionRanges[GetCurrentClass()].bHasGeneratedBody = true;

			ClassData->GeneratedBodyMacroAccessSpecifier = CurrentAccessSpecifier;
		}
		else
		{
			CurrentAccessSpecifier = ACCESS_Public;
		}

		RequireSymbol(TEXT('('), Token.Identifier);
		CompileVersionDeclaration(GetCurrentClass());
		RequireSymbol(TEXT(')'), Token.Identifier);

		ClassData->SetGeneratedBodyLine(InputLine);

		bClassHasGeneratedBody = true;
		return true;
	}
///...
}

GENERATED_UCLASS_BODY导出

在UHT生成宏名字的时候,添加了扫描时记录的所在文件的行号。

也就是说,UHT生成的文件中并没有直接定义GENERATED_UCLASS_BODY的内容,而是根据行号引入了新的宏定义。

///@file: UnrealSourceFile.cpp
FString FUnrealSourceFile::GetGeneratedMacroName(FClassMetaData* ClassData, const TCHAR* Suffix) const
{
	return GetGeneratedMacroName(ClassData->GetGeneratedBodyLine(), Suffix);
}

FString FUnrealSourceFile::GetGeneratedMacroName(int32 LineNumber, const TCHAR* Suffix) const
{
	if (Suffix != nullptr)
	{
		return FString::Printf(TEXT("%s_%d%s"), *GetFileId(), LineNumber, Suffix);
	}

	return FString::Printf(TEXT("%s_%d"), *GetFileId(), LineNumber);
}

GENERATED_UCLASS_BODY定义

编译器看到的GENERATED_UCLASS_BODY宏是再次经过了预处理,预处理之后的宏和UHT定义的宏精确匹配,都包含了文件名对应的ID和所在的行号。

///@file:ObjectMacros.h
// This pair of macros is used to help implement GENERATED_BODY() and 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)

// Include a redundant semicolon at the end of the generated code block, so that intellisense parsers can start parsing
// a new declaration if the line number/generated code is out of date.
#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);

#define GENERATED_USTRUCT_BODY(...) GENERATED_BODY()
#define GENERATED_UCLASS_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_UINTERFACE_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_IINTERFACE_BODY(...) GENERATED_BODY_LEGACY()

CURRENT_FILE_ID

在生成的generated.h文件中,包含了约定的头文件ID的定义,并且位于头文件的最后一行;在定义该宏之前和undef了CURRENT_FILE_ID宏。由于这个限制,对于头文件对应的"generated.h"文件的包含必须位于GENERATED_CLASS_BODY宏所在文件的最后一行(否则会是最后一个包含的generated.h定义的CURRENT_FILE_ID)。

这种机制也是可以实现不同头文件可以互相包含的底层支持机制(不同的头文件有不同的CURRENT_FILE_ID)。

正是这种基于UHT导出的唯一文件ID CURRENT_FILE_ID 和 编译器内置宏定义的行号 LINE 一起唯一确定了GENERATED_UCLASS_BODY的唯一位置

///@file:AIGraph.generated.h
#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID Engine_Source_Editor_AIGraph_Classes_AIGraph_h


PRAGMA_ENABLE_DEPRECATION_WARNINGS

Archive和Record

阅读UHT的代码,可以发现其中还有不少内容是对序列化和反序列化的处理。而序列化和反序列化在UE中的确也广泛使用,包括网络间的同步,对象到磁盘的存储/加载等都会涉及到序列化。

宏定义

预定义了对于结构(Record)的处理:从实现上看,是将“裸数据”UClass的基础上加上了特定的UClass定界符的操作。

///@file:ObjectMacros.h
#define IMPLEMENT_FARCHIVE_SERIALIZER( TClass ) void TClass::Serialize(FArchive& Ar) { TClass::Serialize(FStructuredArchiveFromArchive(Ar).GetSlot().EnterRecord()); }
#define IMPLEMENT_FSTRUCTUREDARCHIVE_SERIALIZER( TClass ) void TClass::Serialize(FStructuredArchive::FRecord Record) { FArchiveUObjectFromStructuredArchive Ar(Record.EnterField(SA_FIELD_NAME(TEXT("BaseClassAutoGen")))); TClass::Serialize(Ar.GetArchive()); Ar.Close(); }
#define DECLARE_FARCHIVE_SERIALIZER( TClass, API ) virtual API void Serialize(FArchive& Ar) override;
#define DECLARE_FSTRUCTUREDARCHIVE_SERIALIZER( TClass, API ) virtual API void Serialize(FStructuredArchive::FRecord Record) override;

扫描

在源代码扫描的过程中,如果扫描到Serialize函数,并且参数是FArchive类型,则自动生成一个对应的FRecord类型声明。

//
// Compile a declaration in Token. Returns 1 if compiled, 0 if not.
//
bool FHeaderParser::CompileDeclaration(FClasses& AllClasses, TArray<UDelegateFunction*>& DelegatesToFixup, FToken& Token)
{
///...

	// Determine if this statement is a serialize function declaration
	if (bEncounteredNewStyleClass_UnmatchedBrackets && IsInAClass() && TopNest->NestType == ENestType::Class)
	{
		while (Token.Matches(TEXT("virtual"), ESearchCase::CaseSensitive) || FString(Token.Identifier).EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
		{
			GetToken(Token);
		}

		if (Token.Matches(TEXT("void"), ESearchCase::CaseSensitive))
		{
			GetToken(Token);
			if (Token.Matches(TEXT("Serialize"), ESearchCase::CaseSensitive))
			{
				GetToken(Token);
				if (Token.Matches(TEXT('(')))
				{
					GetToken(Token);

					ESerializerArchiveType ArchiveType = ESerializerArchiveType::None;
					if (Token.Matches(TEXT("FArchive"), ESearchCase::CaseSensitive))
					{
///...
}

生成例子

#define Engine_Source_Editor_AIGraph_Classes_AIGraph_h_15_ARCHIVESERIALIZER \
	DECLARE_FSTRUCTUREDARCHIVE_SERIALIZER(UAIGraph, NO_API)

gen.cpp内容

UHT生成的宏声明通常以DECLARE开始,包含在对应的generated.h中;对应地,这些声明的实现通常位于对应的gen.cpp文件中,这些内容和DECLARE对应,通常以IMPLENT开始。

同样是以AIGraph.gen.cpp为例,其中包含了对应的宏调用。

这意味着UHT生成的内容主要包括两部分:

原始类的扩充

同样以UAIGraph类为例,生成的代码就包括了扩展的StaticRegisterNativesUAIGraph函数

	void UAIGraph::StaticRegisterNativesUAIGraph()
	{
	}

通过DECLARE_CLASS展开/扩展的StaticClass、StaticPackage、StaticClassCastFlags方法定义

#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.*/ \
	enum {StaticClassFlags=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; \
	}

以及通过

IMPLEMENT_CLASS(UAIGraph, 3896509080);

展开的GetPrivateStaticClass方法定义


// Register a class at startup time.
#define IMPLEMENT_CLASS(TClass, TClassCrc) \
	static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); \
	UClass* TClass::GetPrivateStaticClass() \
	{ \
		static UClass* PrivateStaticClass = NULL; \
		if (!PrivateStaticClass) \
		{ \
			/* 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), \
				PrivateStaticClass, \
				StaticRegisterNatives##TClass, \
				sizeof(TClass), \
				alignof(TClass), \
				(EClassFlags)TClass::StaticClassFlags, \
				TClass::StaticClassCastFlags(), \
				TClass::StaticConfigName(), \
				(UClass::ClassConstructorType)InternalConstructor<TClass>, \
				(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, \
				&TClass::AddReferencedObjects, \
				&TClass::Super::StaticClass, \
				&TClass::WithinClass::StaticClass \
			); \
		} \
		return PrivateStaticClass; \
	}

原始类对应的UClass对象

另一方面,生成代码还包含了一个可以通过扩展的StaticClass接口获得的、描述该类markup信息的UClass对象

	const UE4CodeGen_Private::FClassParams Z_Construct_UClass_UAIGraph_Statics::ClassParams = {
		&UAIGraph::StaticClass,
		nullptr,
		&StaticCppClassTypeInfo,
		DependentSingletons,
		nullptr,
		Z_Construct_UClass_UAIGraph_Statics::PropPointers,
		nullptr,
		UE_ARRAY_COUNT(DependentSingletons),
		0,
		UE_ARRAY_COUNT(Z_Construct_UClass_UAIGraph_Statics::PropPointers),
		0,
		0x001000A0u,
		METADATA_PARAMS(Z_Construct_UClass_UAIGraph_Statics::Class_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UClass_UAIGraph_Statics::Class_MetaDataParams))
	};

posted on 2024-04-02 21:51  tsecer  阅读(7)  评论(0编辑  收藏  举报

导航