UE4版本 4.18
参考:insideUE4
在UE4里面,你无时无刻都会看到类似UFUNCTION()这样的宏。官方文档告诉你,只要在一个函数的前面加上这个宏,然后在括号里面加上BlueprintCallable就可以在编辑器里面调用了。按照他的指示,我们就能让我们的函数实现各种各样特别的功能,那这个效果就是通过UE4的反射系统来实现的。这看起来确实非常棒,不过同时给UE4的反射系统增添了一点神秘感。我们可能一开始尝试着去找一下这个宏的定义,但是翻了几层发现没有头绪,可能也就懒得再去研究他是怎么实现的了。
其实,所谓反射,是程序在运行时进行自检的一种能力,自检什么呢?我认为就是检查自己的C++类,函数,成员变量,结构体等等(对应起来也就是大家在UE4能看到的UCLASS,UFUNCTON,UPROPERTY,USTRUCT后面还会提到)。
那检查这些东西做什么呢?最明显的就是支持蓝图和C++的交互功能,说的更通俗一点,就是可以更自由的控制这些结构,让他在我们想出现的地方出现,让他在我们想使用的地方使用。要知道我们在虚幻4中声明的任意一个类,都是继承于UObject类的,所以他远远不是我们所以为的那个普通的C++类。我们可以使用这个类进行网络复制,执行垃圾回收,让他和蓝图交互等等。而这一切原生的C++是并不支持的,也正是因此虚幻4才构建了一个这样的反射系统。
UnrealBuildTool(UBT,C#):UE4的自定义工具,来编译UE4的逐个模块并处理依赖等。我们编写的Target.cs,Build.cs都是为这个工具服务的。
UnrealHeaderTool (UHT,C++):UE4的C++代码解析生成工具,我们在代码里写的那些宏UCLASS等和#include "*.generated.h"都为UHT提供了信息来生成相应的C++反射代码。
代码编译在两个阶段中进行:1.UHT 被调用。它将解析 C++ 头中引擎相关类元数据,并生成自定义代码,以实现诸多 UObject 相关的功能。2.普通 C++ 编译器被调用,以便对结果进行编译。)
Unreal Build Tool(UBT)和Unreal Header Tool (UHT)两个协同工作来生成运行时反射需要的数据。UBT属性通过扫描头文件,记录任何至少有一个反射类型的头文件的模块。如果其中任意一个头文件从上一次编译起发生了变化,那么 UHT就会被调用来利用和更新反射数据。UHT分析头文件,创建一系列反射数据,并且生成包含反射数据的C++代码(放到每一个模块的moulde.generated.inl中。注:最新版会生成到moudle.generated.cpp中),还有各种帮助函数以及thunk函数(每一个 头文件 .generated.h)
官方文档所给出的基本层次结构
UField
UStruct
UClass (C++ class)
UScriptStruct (C++ struct)
UFunction (C++ function)
UEnum (C++ enumeration)
UProperty (C++ member variable or function parameter)
(Many subclasses for different types)
下图引自InsideUE4
中间过程略为复杂,杂乱,简化为这么几个阶段。
一个UClass的构建过程主要经历几个阶段:
void GetPrivateStaticClassBody(
const TCHAR* PackageName,
const TCHAR* Name,
UClass*& ReturnClass,
void(*RegisterNativeFunc)(),
uint32 InSize,
EClassFlags InClassFlags,
EClassCastFlags InClassCastFlags,
const TCHAR* InConfigName,
UClass::ClassConstructorType InClassConstructor,
UClass::ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller,
UClass::ClassAddReferencedObjectsType InClassAddReferencedObjects,
UClass::StaticClassFunctionType InSuperClassFn,
UClass::StaticClassFunctionType InWithinClassFn,
bool bIsDynamic /*= false*/
) {
ReturnClass = (UClass*)GUObjectAllocator.AllocateUObject(sizeof(UClass), alignof(UClass), true);
ReturnClass = ::new (ReturnClass)
UClass
(
EC_StaticConstructor,
Name,
InSize,
InClassFlags,
InClassCastFlags,
InConfigName,
EObjectFlags(RF_Public | RF_Standalone | RF_Transient | RF_MarkAsNative | RF_MarkAsRootSet),
InClassConstructor,
InClassVTableHelperCtorCaller,
InClassAddReferencedObjects
);
// 省略大部分代码
}
内存构造过程主要是,先分配一个UClass大小的内存,然后构建一个基础的UClass对象,存储该UClass的Name,Flag,构造函数的指针等。
Ue4 主要是通过Static 构建来实现自动化注册的过程。
#define IMPLEMENT_CLASS(TClass, TClassCrc) \
static TClassCompiledInDefer AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); \
or
static FCompiledInDefer Z_CompiledInDefer_UClass_AHelloGameMode(Z_Construct_UClass_AHelloGameMode, &AHelloGameMode::StaticClass, TEXT("/Script/helloworld"), TEXT("AHelloGameMode"), false, nullptr, nullptr, nullptr);
在注册过程中,不是立刻注册,而是通过延迟注册。
通过Register
函数先把ConstructUClass
函数先存放到一个hashmap
中,等待合适的时间进行注册。
主要实现注册逻辑在UObjectBase::DeferredRegister
函数中。
void UObjectBase::DeferredRegister(UClass *UClassStaticClass,const TCHAR* PackageName,const TCHAR* InName)
{
check(Internal::GObjInitialized);
// Set object properties.
UPackage* Package = CreatePackage(nullptr, PackageName);
check(Package);
Package->SetPackageFlags(PKG_CompiledIn);
OuterPrivate = Package;
check(UClassStaticClass);
check(!ClassPrivate);
ClassPrivate = UClassStaticClass;
// Add to the global object table.
AddObject(FName(InName), EInternalObjectFlags::None);
// Make sure that objects disregarded for GC are part of root set.
check(!GUObjectArray.IsDisregardForGC(this) || GUObjectArray.IndexToObject(InternalIndex)->IsRootSet());
}
注册过程主要是收集UObject的一些基本信息。
class COREUOBJECT_API UObjectBase
{
private:
EObjectFlags ObjectFlags; //对象标志,定义了对象各种状态和特征
int32 InternalIndex; //对象的存储索引
UClass* ClassPrivate;
FName NamePrivate;
UObject* OuterPrivate;
};
class COREUOBJECT_API UStruct : public UField
{
private:
UStruct* SuperStruct;
};
在注册的过程中收集了ClassPrivate1,OuterPrivate2,NamePrivate3基本信息。
同时在AddObject中把UClass之前的联系存储到HashMap中,以便在后续通过Hash结构找到UClass。
主要讨论UFunction和UProperty的构造
UFUNCTION主要是通过RegisterFunctions存储函数指针,ConstructUFunction构建UFunction的属性。
void ConstructUFunction(UFunction*& OutFunction, const FFunctionParams& Params)
{
UObject* (*OuterFunc)() = Params.OuterFunc;
UFunction* (*SuperFunc)() = Params.SuperFunc;
UObject* Outer = OuterFunc ? OuterFunc() : nullptr;
UFunction* Super = SuperFunc ? SuperFunc() : nullptr;
if (OutFunction)
{
return;
}
UFunction* NewFunction;
if (Params.FunctionFlags & FUNC_Delegate)
{
NewFunction = new (EC_InternalUseOnlyConstructor, Outer, UTF8_TO_TCHAR(Params.NameUTF8), Params.ObjectFlags) UDelegateFunction(
FObjectInitializer(),
Super,
Params.FunctionFlags,
Params.StructureSize
);
}
OutFunction = NewFunction;
ConstructUProperties(NewFunction, Params.PropertyArray, Params.NumProperties);
NewFunction->Bind();
NewFunction->StaticLink();
}
构建UFunction的基础信息,OuterPrivate,SuperStruct这些基础信息,同时为函数中的参数也构建反射信息。
而Bind主要是为绑定函数指针到正确的位置。
UProperty的构建相对来说比较简单。
void ConstructUProperties(UObject* Outer, const FPropertyParamsBase* const* PropertyArray, int32 NumProperties)
{
while (NumProperties)
{
ConstructUProperty(Outer, PropertyArray, NumProperties);
}
}
在ConstructUProperty中根据UPropertyType构建生成一个UProperty。
UClass的绑定链接主要实现在hashmap的构建。
UFunction的构建分为两部分,一部分是hashmap的构建,一部分是Children链表指针的构建。
UProperty的构建也是在Children链表指针的构建。
Ue4用了这种方法存储了这个构造函数的指针然后存储到初始构造函数中。
#define DEFINE_DEFAULT_CONSTRUCTOR_CALL(TClass) \
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(); }
#define DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(TClass) \
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }
然后在分配内存的时候把该函数指针存到ClassConstructor中去,当我们CreateDefaultObject的时候就会调用该构建函数构建一个CDO对象。
(*ClassConstructor)(FObjectInitializer(ClassDefaultObject, ParentDefaultObject, false, bShouldInitializeProperties));