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