In-depth UE - UObject (11) type system construction - construction binding link

introduction

In the previous article, we introduced the final stage of type registration. A RegisterFn call was made for each enum, struct, and class (please read the previous article if you forgot the timing of the call), and these RegisterFn actually point to the functions in the generated code. This article will explain the generation and construction of type objects in this. According to the generation order, which is also the calling order, the generation of UEnum and UScriptStruct, and the continued construction of UClass will be explained one by one. Notice:

  1. Remove the HotReload and MetaData parts of the code (that are just to provide additional information to the editor).
  2. The definition of each Params in the code can be viewed in the "Code Generation and Reconstruction" chapter, or you can open the source code yourself to compare. It is relatively simple and will not be listed here.

One

Let’s start with the soft ones. This is the MyEnum used for testing:

UENUM(Blueprintable,meta=(EnumDisplayNameFn="GetMyEnumDisplayName"))
enum class MyEnum:uint8
{
    Dance   UMETA(DisplayName = "MyDance"),
    Rain    UMETA(DisplayName = "MyRain"),
    Song    UMETA(DisplayName = "MySong")
};

FText GetMyEnumDisplayName(int32 val)   //可以提供一个自定义函数给枚举来额外显示
{
    MyEnum enumValue = (MyEnum)val;
    switch (enumValue)
    {
    case MyEnum::Dance:
        return FText::FromString(TEXT("Hello_Dance"));
    case MyEnum::Rain:
        return FText::FromString(TEXT("Hello_Rain"));
    case MyEnum::Song:
        return FText::FromString(TEXT("Hello_Song"));
    default:
        return FText::FromString(TEXT("Invalid MyEnum"));
    }
}

Key to generating code:

static UEnum* MyEnum_StaticEnum()   //RegisterFn指向该函数
{
    static UEnum* Singleton = nullptr;
    if (!Singleton)
    {
        Singleton = GetStaticEnum(Z_Construct_UEnum_Hello_MyEnum, Z_Construct_UPackage__Script_Hello(), TEXT("MyEnum"));
    }
    return Singleton;
}

static FCompiledInDeferEnum Z_CompiledInDeferEnum_UEnum_MyEnum(MyEnum_StaticEnum, TEXT("/Script/Hello"), TEXT("MyEnum"), false, nullptr, nullptr);    //收集点

UEnum* Z_Construct_UEnum_Hello_MyEnum() //实际调用点
{
    static UEnum* ReturnEnum = nullptr;
    if (!ReturnEnum)
    {
        static const UE4CodeGen_Private::FEnumeratorParam Enumerators[] = {
            { "MyEnum::Dance", (int64)MyEnum::Dance }, //注意枚举项的名字是以"枚举::"开头的
            { "MyEnum::Rain", (int64)MyEnum::Rain },
            { "MyEnum::Song", (int64)MyEnum::Song },
        };

        static const UE4CodeGen_Private::FEnumParams EnumParams = {
            (UObject*(*)())Z_Construct_UPackage__Script_Hello,
            UE4CodeGen_Private::EDynamicType::NotDynamic,
            "MyEnum",
            RF_Public|RF_Transient|RF_MarkAsNative,
            GetMyEnumDisplayName,//一般为nullptr,我们自定义了一个,所以这里才有
            (uint8)UEnum::ECppForm::EnumClass,
            "MyEnum",
            Enumerators,
            ARRAY_COUNT(Enumerators),
            METADATA_PARAMS(Enum_MetaDataParams, ARRAY_COUNT(Enum_MetaDataParams))
        };
        UE4CodeGen_Private::ConstructUEnum(ReturnEnum, EnumParams);//最终生成点
    }
    return ReturnEnum;
}

Generate the collection point in the code and register MyEnum_StaticEnum to RegisterFn. When called, the internal GetStaticEnum will call Z_Construct_UEnum_Hello_MyEnum in the entry. Z_Construct_UEnum_Hello_MyEnum is actually relatively simple internally. It defines enumeration item parameters and enumeration parameters, and finally sends them to UE4CodeGen_Private::ConstructUEnum for calls.

void ConstructUEnum(UEnum*& OutEnum, const FEnumParams& Params)
{
    UObject* (*OuterFunc)() = Params.OuterFunc;
    UObject* Outer = OuterFunc ? OuterFunc() : nullptr; //先确保创建Outer
    if (OutEnum) {return;}  //防止重复构造

    UEnum* NewEnum = new (EC_InternalUseOnlyConstructor, Outer, UTF8_TO_TCHAR(Params.NameUTF8), Params.ObjectFlags) UEnum(FObjectInitializer());    //创建一个UEnum
    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);
    }
    //设置枚举项数组
    NewEnum->SetEnums(EnumNames, (UEnum::ECppForm)Params.CppForm, Params.DynamicType == EDynamicType::NotDynamic);
    NewEnum->CppType = UTF8_TO_TCHAR(Params.CppTypeUTF8);  //cpp名字

    if (Params.DisplayNameFunc)
    {
        NewEnum->SetEnumDisplayNameFn(Params.DisplayNameFunc);  //设置自定义显示名字回调
    }
}

Basically the code is self-explanatory, there are only two things to note: First, the call to OuterFunc() is used to first ensure that the UPackage belonging to the outside world exists. The second is the construction of UEnum. The source code uses the method of overloading new. Note that it is not placement new. This new method is defined in DECLARE_CLASSthe macro:

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

Therefore, StaticAllocateObject will be triggered first to allocate a piece of object memory (simple initialization, which will be explained in the subsequent object allocation chapters), and then the constructor of UEnum will be called, and the enumeration items will be filled in after the construction is completed.

Thinking: Why not use our commonly used NewObject to create UEnum?

In fact, UEnum* NewEnum= NewObject< UEnum>(Outer, UTF8_TO_TCHAR(Params.NameUTF8), Params.ObjectFlags);it is feasible to change here. So what's the difference between the two? Very little. The processes of the two are generally the same, except that the value of FObjectInitializer is different. The new constructor will call the FObjectInitializer()default constructor, while NewObject will call it FObjectInitializer(Result, InTemplate, bCopyTransientsFromClassDefaults, true, InInstanceGraph). The difference lies in the fourth parameter bShouldInitializePropsFromArchetype. The former defaults to =false, which will skip the initialization of attribute values ​​in UClass during the construction process. Because there are no attributes in enum, skipping this process will slightly improve performance, but it is minimal.

Continue to look inside SetEnums

bool UEnum::SetEnums(TArray<TPair<FName, int64>>& InNames, UEnum::ECppForm InCppForm, bool bAddMaxKeyIfMissing)
{
    if (Names.Num() > 0)
    {
        RemoveNamesFromMasterList();   //去除之前的名字
    }
    Names   = InNames;
    CppForm = InCppForm;
    if (bAddMaxKeyIfMissing)
    {
        if (!ContainsExistingMax())
        {
            FName MaxEnumItem = *GenerateFullEnumName(*(GenerateEnumPrefix() + TEXT("_MAX")));
            if (LookupEnumName(MaxEnumItem) != INDEX_NONE)
            {
                // the MAX identifier is already being used by another enum
                return false;
            }
            Names.Emplace(MaxEnumItem, GetMaxEnumValue() + 1);
        }
    }
    AddNamesToMasterList();
    return true;
}

The code is also very simple, enum is simple. There are two operations that are more important:

  1. RemoveNamesFromMasterList and AddNamesToMasterList maintain UEnum together static TMap<FName, UEnum*> AllEnumNames;. This data structure allows us to search for enumeration types through an enumeration item name. For example, use LookupEnumName through "MyEnum::Dance" to return the enumerated UEnum*.
  2. bAddMaxKeyIfMissing is true in the native case, so the enumeration defined in C++ will automatically add an item to MyEnum::MyEnum_MAX=最大值+1facilitate use in blueprints.

Then the enum is over!

UScriptStruct

Still, let’s take a look at the test code of the structure:

USTRUCT(BlueprintType)
struct HELLO_API FMyStruct
{
    GENERATED_BODY()
    UPROPERTY(BlueprintReadWrite)
    float Score;
};

The structure in UE cannot inherit from c++ struct, but it can inherit from struct marked with USTRUCT, and UFUNCTION cannot be added to it. The above test code is very simple, and the generated code (some layout adjustments have been made for readability):

class UScriptStruct* FMyStruct::StaticStruct()  //RegisterFn指向该函数
{
    static class UScriptStruct* Singleton = NULL;
    if (!Singleton)
    {
        Singleton = GetStaticStruct(Z_Construct_UScriptStruct_FMyStruct, Z_Construct_UPackage__Script_Hello(), TEXT("MyStruct"), sizeof(FMyStruct), Get_Z_Construct_UScriptStruct_FMyStruct_CRC());
    }
    return Singleton;
}

static FCompiledInDeferStruct Z_CompiledInDeferStruct_UScriptStruct_FMyStruct(FMyStruct::StaticStruct, TEXT("/Script/Hello"), TEXT("MyStruct"), false, nullptr, nullptr);     //收集点

static struct FScriptStruct_Hello_StaticRegisterNativesFMyStruct
{
    FScriptStruct_Hello_StaticRegisterNativesFMyStruct()
    {
        UScriptStruct::DeferCppStructOps(FName(TEXT("MyStruct")),new UScriptStruct::TCppStructOps<FMyStruct>);
    }
} ScriptStruct_Hello_StaticRegisterNativesFMyStruct; //收集点

struct Z_Construct_UScriptStruct_FMyStruct_Statics
{
    static void* NewStructOps() //创建结构操作辅助类
    {
        return (UScriptStruct::ICppStructOps*)new UScriptStruct::TCppStructOps<FMyStruct>();
    }
    //属性参数...
    //结构参数
    static const UE4CodeGen_Private::FStructParams ReturnStructParams= 
    {
        (UObject* (*)())Z_Construct_UPackage__Script_Hello,//Outer
        nullptr,    //构造基类的函数指针
        &NewStructOps,//构造结构操作类的函数指针
        "MyStruct",//结构名字
        RF_Public|RF_Transient|RF_MarkAsNative, //对象标记
        EStructFlags(0x00000201),   //结构标记
        sizeof(FMyStruct),//结构大小
        alignof(FMyStruct), //结构内存对齐
        PropPointers, ARRAY_COUNT(PropPointers) //属性列表
    };
};

UScriptStruct* Z_Construct_UScriptStruct_FMyStruct() //真正的构造实现
{
    static UScriptStruct* ReturnStruct = nullptr;
    if (!ReturnStruct)
    {
        UE4CodeGen_Private::ConstructUScriptStruct(ReturnStruct, Z_Construct_UScriptStruct_FMyStruct_Statics::ReturnStructParams);
    }
    return ReturnStruct;
}

Like Enum's routine, RegisterFn points to FMyStruct::StaticStruct()the function, which will continue to be called internally Z_Construct_UScriptStruct_FMyStruct. Ultimately it is ConstructUScriptStructused to generate UScriptStruct through parameters. As for those parameters, I can clearly understand them at a glance through the comments in the code. They are just structures within structures.

void ConstructUScriptStruct(UScriptStruct*& OutStruct, const FStructParams& Params)
{
    UObject* Outer = Params.OuterFunc ? Params.OuterFunc() : nullptr;//构造Outer
    UScriptStruct* Super = Params.SuperFunc ? Params.SuperFunc() : nullptr;//构造SuperStruct
    UScriptStruct::ICppStructOps* StructOps = Params.StructOpsFunc ? Params.StructOpsFunc() : nullptr;//构造结构操作类

    if (OutStruct) {return;}
    //构造UScriptStruct
    UScriptStruct* NewStruct = new(EC_InternalUseOnlyConstructor, Outer, UTF8_TO_TCHAR(Params.NameUTF8), Params.ObjectFlags) UScriptStruct(FObjectInitializer(), Super, StructOps, (EStructFlags)Params.StructFlags, Params.SizeOf, Params.AlignOf);
    OutStruct = NewStruct;
    //构造属性集合
    ConstructUProperties(NewStruct, Params.PropertyArray, Params.NumProperties);
    //链接
    NewStruct->StaticLink();
}

In the same mode call as UEnum, the dependent Outer, Super and CppStructOps are first constructed in sequence. Then overload new is still used to construct UScriptStruct. But there is one more call in the constructor of UScriptStruct PrepareCppStructOps(to mention briefly, it mainly extracts features from CppStructOps and then stores them in StructFlags). The next step ConstructUPropertiesis to construct a UProperty* array from the property parameter array, which will be reused when constructing UClass* later. Therefore, we will explain it later. All composite types (inherited from UStruct) will then be called StaticLinkto link sub-properties. The function of Link will be explained later.

The role of ICppStructOps

When many friends look at the source code, they may be confused about the ICppStructOps class and the template subclass TCppStructOps<CPPSTRUCT> defined in UScriptStruct. In fact, they are a common architectural pattern in C++. They use a virtual function base class to define some public operations, and then use a specific template subclass to implement them, so that they can both save types and have public operation interfaces.

For UE4, ICppStructOps defines some common operations of this structure. The detection of some characteristics of this C++ structure is left to TStructOpsTypeTraits<CPPSTRUCT> in the TCppStructOps<CPPSTRUCT> class. Some C++ structure information cannot be detected through templates, so we need to manually mark and provide it, so the specific code is:

template <class CPPSTRUCT>
struct TStructOpsTypeTraitsBase2
{
    enum
    {
        WithZeroConstructor = false, // 0构造,内存清零后就可以了,说明这个结构的默认值就是0
        WithNoInitConstructor = false, // 有个ForceInit的参数的构造,用来专门构造出0值结构来
        WithNoDestructor = false, // 是否没有结构有自定义的析构函数, 如果没有析构的话,DestroyStruct里面就可以省略调用析构函数了。默认是有的。结构如果是pod类型,则肯定没有析构。
        WithCopy = !TIsPODType<CPPSTRUCT>::Value, // 是否结构有自定义的=赋值函数。如果没有的话,在CopyScriptStruct的时候就只需要拷贝内存就可以了
        WithIdenticalViaEquality = false, // 用==来比较结构
        WithIdentical = false, // 有一个自定义的Identical函数来专门用来比较,和WithIdenticalViaEquality互斥
        WithExportTextItem = false, // 有一个ExportTextItem函数来把结构值导出为字符串
        WithImportTextItem = false, // 有一个ImportTextItem函数把字符串导进结构值
        WithAddStructReferencedObjects = false, // 有一个AddStructReferencedObjects函数用来添加结构额外的引用对象
        WithSerializer = false, // 有一个Serialize函数用来序列化
        WithStructuredSerializer = false, // 有一个结构结构Serialize函数用来序列化
        WithPostSerialize = false, // 有一个PostSerialize回调用来在序列化后调用
        WithNetSerializer = false, // 有一个NetSerialize函数用来在网络复制中序列化
        WithNetDeltaSerializer = false, // 有一个NetDeltaSerialize函数用来在之前NetSerialize的基础上只序列化出差异来,一般用在TArray属性上进行优化
        WithSerializeFromMismatchedTag = false, // 有一个SerializeFromMismatchedTag函数用来处理属性tag未匹配到的属性值,一般是在结构进行升级后,但值还是原来的值,这个时候用来把旧值升级到新结构时使用
        WithStructuredSerializeFromMismatchedTag = false, // SerializeFromMismatchedTag的结构版本
        WithPostScriptConstruct = false,// 有一个PostScriptConstruct函数用在蓝图构造脚本后调用
        WithNetSharedSerialization = false, // 指明结构的NetSerialize函数不需要用到UPackageMap
    };
};
template<class CPPSTRUCT>
struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2<CPPSTRUCT>
{
};

These enumeration values ​​define the characteristics of a structure, and I have explained them one by one in the source code. Going back to the interfaces in ICppStructOps, most of the internal implementations call different versions of functions through the structure of TStructOpsTypeTraits<CPPSTRUCT>. The operations of the structure can be divided into:

  • 构造:HasNoopConstructor、HasZeroConstructor、Construct、HasPostScriptConstruct、PostScriptConstruct、IsAbstract
  • Destruction: HasDestructor, Destruct
  • Copy: IsPlainOldData, HasCopy, Copy
  • Compare:HasIdentical,Identical
  • Import and export: HasExportTextItem, ExportTextItem, HasImportTextItem, ImportTextItem
  • GC:HasAddStructReferencedObjects、AddStructReferencedObjects
  • 序列化:HasSerializer、HasStructuredSerializer、Serialize、HasPostSerialize、PostSerialize、HasNetSerializer、HasNetSharedSerialization、NetSerialize、HasNetDeltaSerializer、NetDeltaSerialize、HasSerializeFromMismatchedTag、HasStructuredSerializeFromMismatchedTag、SerializeFromMismatchedTag、StructuredSerializeFromMismatchedTag

With the public interface of ICppStructOps and the above specialized information, UE4 can choose the optimal step when constructing, destructing or serializing the structure internally (for example, when copying, it only needs to copy the memory directly without calling Assignment function), during GC, you can also tell UE4 that there may be additional UObject* objects inside this structure. This allows UE4 to achieve higher performance for this structure.

For example, for the common FVector, such a specialization is defined in the source code to describe its characteristics:

template<>
struct TStructOpsTypeTraits<FVector> : public TStructOpsTypeTraitsBase2<FVector>
{
    enum 
    {
        WithNoInitConstructor = true,
        WithZeroConstructor = true,
        WithNetSerializer = true,
        WithNetSharedSerialization = true,
        WithSerializer = true,
    };
};

At a glance, we know that FVector has a 0-value constructor and a Serializer function. For our own defined UStruct, if necessary, we can also define such a template specialization to provide more detailed structural information.

UClass

Next is our most commonly used Class and Interface, let’s play together:

UINTERFACE(BlueprintType, Blueprintable)
class HELLO_API UMyInterface:public UInterface
{
    GENERATED_BODY()
};
class IMyInterface
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
        void NativeInterfaceFunc();

    UFUNCTION(BlueprintCallable, BlueprintImplementableEvent)
        void ImplementableInterfaceFunc();
};

UCLASS(BlueprintType, Blueprintable)
class HELLO_API UMyClass :public UObject, public IMyInterface
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadWrite)
        float Score;
public:
    UFUNCTION(BlueprintCallable, Category = "Hello")
        int32 Func(float param1);    //C++实现,蓝图调用

    UFUNCTION(BlueprintNativeEvent, Category = "Hello")
        void NativeFunc();  //C++实现默认版本,蓝图可重载实现

    UFUNCTION(BlueprintImplementableEvent, Category = "Hello")
        void ImplementableFunc();   //C++不实现,蓝图实现
};

Key parts of the generated code (with some layout adjustments):

//函数参数...
struct Z_Construct_UClass_UMyClass_Statics
{
    //依赖项列表
    static UObject* (*const DependentSingletons[])()=
    {
        (UObject* (*)())Z_Construct_UClass_UObject,//依赖基类UObject
        (UObject* (*)())Z_Construct_UPackage__Script_Hello,//依赖所属于的Hello模块
    };
    //属性参数...
    //函数参数...
    //接口
    static const UE4CodeGen_Private::FImplementedInterfaceParams InterfaceParams[]= 
    {
        {
            Z_Construct_UClass_UMyInterface_NoRegister,//构造UMyInterface所属的UClass*函数指针
            (int32)VTABLE_OFFSET(UMyClass, IMyInterface),//多重继承的指针偏移
            false   //是否是在蓝图实现
        }
    };

    static const FCppClassTypeInfoStatic StaticCppClassTypeInfo= {
        TCppClassTypeTraits<UMyClass>::IsAbstract,//c++类信息,是否是虚类
    };

    static const UE4CodeGen_Private::FClassParams ClassParams = 
    {
        &UMyClass::StaticClass,//取出UClass*的函数指针
        DependentSingletons, ARRAY_COUNT(DependentSingletons),//依赖项
        0x001000A0u,//类标志
        FuncInfo, ARRAY_COUNT(FuncInfo),//函数列表
        PropPointers, ARRAY_COUNT(PropPointers),//属性列表
        nullptr,//Config文件名
        &StaticCppClassTypeInfo,//c++类信息
        InterfaceParams, ARRAY_COUNT(InterfaceParams)//接口列表
    };
};

UClass* Z_Construct_UClass_UMyClass()
{
    static UClass* OuterClass = nullptr;
    if (!OuterClass)
    {
        UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_UMyClass_Statics::ClassParams);
    }
    return OuterClass;
}

IMPLEMENT_CLASS(UMyClass, 4008851639); //收集点
static FCompiledInDefer Z_CompiledInDefer_UClass_UMyClass(Z_Construct_UClass_UMyClass, &UMyClass::StaticClass, TEXT("/Script/Hello"), TEXT("UMyClass"), false, nullptr, nullptr, nullptr); //收集点

Class has more information and can have interfaces, properties and functions. Properties and functions will be explained later. Because the extra MyInterface is also a UObject class, it will actually generate a UClass* object and add functions to UMyInterface. The pattern is the same as UMyClass, so I won’t go into details. However, because UMyClass inherits from IMyInterface, it is necessary to add additional interface inheritance information to UClass* of UMyClass. The implementation of interface in C++ uses multiple inheritance, so there is the problem of object base class pointer offset (students who don’t understand this should take the initiative to take a basic C++ course), so each item in FImplementedInterfaceParams has a pointer offset obtained with VTABLE_OFFSET. Move, with Offset, you can get the IMyInterface* address based on Obj+Offset, and then call the interface function. This is also the logical implementation of GetInterfaceAddress.

FCppClassTypeInfoStaticIn fact, the function of UClass is similar to that of structure ICppStructOps. They both identify type information in native C++. However, UClass has a different function from UStruct. There are no pure memory construction and destruction operations, and there must be a serializer, so there are currently FCppClassTypeInfoStaticonly A bIsAbstract to determine whether the class is a virtual class.

Constructed code (with some adjustments for formatting and readability):

void ConstructUClass(UClass*& OutClass, const FClassParams& Params)
{
    if (OutClass && (OutClass->ClassFlags & CLASS_Constructed)) {return;}  //防止重复构造
    for(int i=0;i<Params.NumDependencySingletons;++i)
    {
        Params.DependencySingletonFuncArray[i]();   //构造依赖的对象
    }

    UClass* NewClass = Params.ClassNoRegisterFunc();    //取得先前生成的UClass*,NoRegister是指没有经过DeferRegister
    OutClass = NewClass;

    if (NewClass->ClassFlags & CLASS_Constructed) {return;}//防止重复构造

    UObjectForceRegistration(NewClass); //确保此UClass*已经注册

    NewClass->ClassFlags |= (EClassFlags)(Params.ClassFlags | CLASS_Constructed);//标记已经构造

    if ((NewClass->ClassFlags & CLASS_Intrinsic) != CLASS_Intrinsic)
    {
        check((NewClass->ClassFlags & CLASS_TokenStreamAssembled) != CLASS_TokenStreamAssembled);
        NewClass->ReferenceTokenStream.Empty();//对于蓝图类需要重新生成一下引用记号流
    }
    //构造函数列表
    NewClass->CreateLinkAndAddChildFunctionsToMap(Params.FunctionLinkArray, Params.NumFunctions);
    //构造属性列表
    ConstructUProperties(NewClass, Params.PropertyArray, Params.NumProperties);

    if (Params.ClassConfigNameUTF8)
    {   //配置文件名
        NewClass->ClassConfigName = FName(UTF8_TO_TCHAR(Params.ClassConfigNameUTF8));
    }

    NewClass->SetCppTypeInfoStatic(Params.CppClassInfo);//C++类型信息

    if (Params.NumImplementedInterfaces)
    {
        NewClass->Interfaces.Reserve(Params.NumImplementedInterfaces);
        for(int i=0;i<Params.Params.NumImplementedInterfaces;++i)
        {
            const auto& ImplementedInterface = Params.ImplementedInterfaceArray[i];
            UClass* (*ClassFunc)() = ImplementedInterface.ClassFunc;
            UClass* InterfaceClass = ClassFunc ? ClassFunc() : nullptr;//取得UMyInterface所属于的UClass*对象

            NewClass->Interfaces.Emplace(InterfaceClass, ImplementedInterface.Offset, ImplementedInterface.bImplementedByK2);//添加实现的接口
        }
    }

    NewClass->StaticLink();//链接
}

The construction process is also very simple. It is nothing more than making sure that the dependent object already exists, and then adding various information: functions, properties, configuration file names, C++ type information and interfaces to UClass* one by one. There are only three important steps: ConstructUPropertiesthe same as UScriptStruct; the implementation of the interface is TArray<FImplementedInterface> Interfacesexpressed through the array in UClass*; CreateLinkAndAddChildFunctionsToMapcreating the function list.

void UClass::CreateLinkAndAddChildFunctionsToMap(const FClassFunctionLinkInfo* Functions, uint32 NumFunctions)
{
    for (; NumFunctions; --NumFunctions, ++Functions)
    {
        const char* FuncNameUTF8 = Functions->FuncNameUTF8;
        UFunction*  Func         = Functions->CreateFuncPtr();//调用构造UFunction*对象

        Func->Next = Children;
        Children = Func;//新函数挂在UField*链表的开头

        AddFunctionToFunctionMap(Func, FName(UTF8_TO_TCHAR(FuncNameUTF8)));
        //内部实现是:FuncMap.Add(FuncName, Function);添加到FuncMap里
    }
}

Just create UFunction* objects one by one and add them to FuncMap. The interesting thing here is that Children is actually a singly linked list of UField*, and adding attributes and functions are directly linked to the head of the linked list. It can be known from the construction order (CreateLinkAndAddChildFunctionsToMap first, ConstructUPerties second) that the final order of Children is all UProperty* first, and then all UFunction*. But the order of UProperty* is consistent with the order defined in the code, because the PropPointers in the code generated by UHT happen to be arranged in reverse order. The order of UFunction* is the reverse order after sorting by function name.

UPackage

We noticed that there is a process of constructing Outer at the beginning of constructing UEnum, UScriptScript and UClass. This OuterFunc actually points to the construction of the Module's Package. This function is usually in the module's init.gen.cpp.

//Hello.init.gen.cpp
UPackage* Z_Construct_UPackage__Script_Hello()
{
    static UPackage* ReturnPackage = nullptr;
    if (!ReturnPackage)
    {   //先构造代码里的Dynamic Delegate
        static UObject* (*const SingletonFuncArray[])() = {
            (UObject* (*)())Z_Construct_UDelegateFunction_Hello_MyDynamicSinglecastDelegate_One__DelegateSignature,
            (UObject* (*)())Z_Construct_UDelegateFunction_Hello_MyDynamicMulticastDelegate_One__DelegateSignature,
        };
        static const UE4CodeGen_Private::FPackageParams PackageParams = {
            "/Script/Hello",
            PKG_CompiledIn | 0x00000000,
            0x135222B4,
            0x392E1CFD,
            SingletonFuncArray, ARRAY_COUNT(SingletonFuncArray),
            METADATA_PARAMS(nullptr, 0)
        };
        UE4CodeGen_Private::ConstructUPackage(ReturnPackage, PackageParams);//构造Pacakge
    }
    return ReturnPackage;
}

//DECLARE_DYNAMIC_DELEGATE_OneParam(FMyDynamicSinglecastDelegate_One, int32, Value);

The only thing that needs to be reminded is that for the dynamic delegate (DynamicDelegate) defined in the code, UFunction* objects will be generated accordingly. These scattered DynamicDelegate can be defined outside the class, so they do not belong to any UClass* object, so they have to belong to the module Package. The construction code is also relatively simple:

void ConstructUPackage(UPackage*& OutPackage, const FPackageParams& Params)
{
    if (OutPackage) {return;}

    UPackage* NewPackage = CastChecked<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), nullptr, FName(UTF8_TO_TCHAR(Params.NameUTF8)), false, false));//找到之前创建的Package
    OutPackage = NewPackage;

    NewPackage->SetPackageFlags(Params.PackageFlags);//设定标记
    NewPackage->SetGuid(FGuid(Params.BodyCRC, Params.DeclarationsCRC, 0u, 0u));

    for (UObject* (*const *SingletonFunc)() = Params.SingletonFuncArray, *(*const *SingletonFuncEnd)() = SingletonFunc + Params.NumSingletons; SingletonFunc != SingletonFuncEnd; ++SingletonFunc)
    {
        (*SingletonFunc)();//调用构造前提对象
    }
}

It should be noted that at this time of ConstructUPackage, the UPackage has actually been created before the UClass* object Register, so you only need to search it, then set the information tag, and finally create the UFunction* object in this UPackage.

UProperty

Finally, I finished talking about the structure of the big guy earlier, and now I’m going to talk about the small attributes.

//一个个属性的参数
static const UE4CodeGen_Private::FFloatPropertyParams NewProp_Score = 
{ 
    UE4CodeGen_Private::EPropertyClass::Float, 
    "Score", 
    RF_Public|RF_Transient|RF_MarkAsNative,//对象标记
    (EPropertyFlags)0x0010000000000004, //属性标记
    1,  //数组维度,固定的数组的大小
    nullptr, //RepNotify函数的名称
    STRUCT_OFFSET(FMyStruct, Score) //属性的结构偏移地址
};
 //结构参数数组,会发送给ConstructUProperties来构造。
static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[] = 
{
    &NewProp_Score
};

ConstructUPerties actually traverses the array and calls ConstructUPerty:

void ConstructUProperty(UObject* Outer, const FPropertyParamsBase* const*& PropertyArray, int32& NumProperties)
{
    const FPropertyParamsBase* PropBase = *PropertyArray++;
    uint32 ReadMore = 0;
    UProperty* NewProp = nullptr;
    switch (PropBase->Type)
    {
       case EPropertyClass::Array:
        {
            const FArrayPropertyParams* Prop = (const FArrayPropertyParams*)PropBase;
            NewProp = new (EC_InternalUseOnlyConstructor, Outer, UTF8_TO_TCHAR(Prop->NameUTF8), Prop->ObjectFlags) UArrayProperty(FObjectInitializer(), EC_CppProperty, Prop->Offset, Prop->PropertyFlags);//构造Property对象

            // Next property is the array inner
            ReadMore = 1;//需要一个子属性
        }
        break;
        //case其他的各种类型属性
    }

    NewProp->ArrayDim = PropBase->ArrayDim;//设定属性维度,单属性为1,int32 prop[10]这种的为10
    if (PropBase->RepNotifyFuncUTF8)
    {   //属性的复制通知函数名
        NewProp->RepNotifyFunc = FName(UTF8_TO_TCHAR(PropBase->RepNotifyFuncUTF8));
    }

    --NumProperties;
    for (; ReadMore; --ReadMore)
    {   //构造子属性,注意这里以现在的属性NewProp为Outer
        ConstructUProperty(NewProp, PropertyArray, NumProperties);
    }
}

UHT will analyze the property types defined in our code to generate different FPropertyParams types. According to different EPropertyClass, construct and generate different types of UProperty*. At the same time, for some composite attribute types, 1 or 2 sub-attributes need to be generated.

This table is provided for everyone to compare. Another thing to note is that the constructor of UProperty will add itself to Outer:

void UProperty::Init()
{
    GetOuterUField()->AddCppProperty(this);//AddCppProperty是个虚函数
}
//重载
void UArrayProperty::AddCppProperty( UProperty* Property )
{
    Inner = Property;   //元素属性
}
void UMapProperty::AddCppProperty( UProperty* Property )
{
    if (!KeyProp) {KeyProp = Property;}//第一个是键属性
    else {ValueProp = Property;}//第二个是值属性
}
void USetProperty::AddCppProperty( UProperty* Property )
{
    ElementProp = Property;//元素属性
}

void UEnumProperty::AddCppProperty(UProperty* Inner)
{
    UnderlyingProp = CastChecked<UNumericProperty>(Inner);//依靠的整数属性
}
void UStruct::AddCppProperty( UProperty* Property )
{
    Property->Next = Children;
    Children       = Property;//新属性挂在UField*链表的开头
}

UFunction

After talking about attributes, let’s pamper functions. Please scroll up to view the test code in MyClass.

//测试函数:int32 Func(float param1);
void UMyClass::ImplementableFunc()  //UHT为我们生成了函数实体
{
    ProcessEvent(FindFunctionChecked("ImplementableFunc"),NULL);
}
void UMyClass::NativeFunc() //UHT为我们生成了函数实体,但我们可以自定义_Implementation
{
    ProcessEvent(FindFunctionChecked("NativeFunc"),NULL);
}
void UMyClass::StaticRegisterNativesUMyClass()  //之前的Native函数收集点
{
    UClass* Class = UMyClass::StaticClass();
    static const FNameNativePtrPair Funcs[] = {
        { "Func", &UMyClass::execFunc },
        { "NativeFunc", &UMyClass::execNativeFunc },
    };
    FNativeFunctionRegistrar::RegisterFunctions(Class, Funcs, ARRAY_COUNT(Funcs));
}
struct Z_Construct_UFunction_UMyClass_Func_Statics
{
    struct MyClass_eventFunc_Parms  //把所有参数打包成一个结构来存储
    {
        float param1;
        int32 ReturnValue;
    };
    static const UE4CodeGen_Private::FIntPropertyParams NewProp_ReturnValue= 
    { 
        UE4CodeGen_Private::EPropertyClass::Int, 
        "ReturnValue", 
        RF_Public|RF_Transient|RF_MarkAsNative,
        (EPropertyFlags)0x0010000000000580,
        1, 
        nullptr, 
        STRUCT_OFFSET(MyClass_eventFunc_Parms, ReturnValue) 
    };

    static const UE4CodeGen_Private::FFloatPropertyParams NewProp_param1 =
    { 
        UE4CodeGen_Private::EPropertyClass::Float,
        "param1", 
        RF_Public|RF_Transient|RF_MarkAsNative, 
        (EPropertyFlags)0x0010000000000080, 
        1,
        nullptr, 
        STRUCT_OFFSET(MyClass_eventFunc_Parms, param1) 
    };
    //函数的子属性
    static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[]= 
    {
        &NewProp_ReturnValue,   //返回值也用属性表示
        &NewProp_param1,        //参数用属性表示
    };
    //函数的参数
    static const UE4CodeGen_Private::FFunctionParams FuncParams=
    { 
        (UObject*(*)())Z_Construct_UClass_UMyClass, //外部对象
        "Func", //名字
        RF_Public|RF_Transient|RF_MarkAsNative, //对象标记
        nullptr, //父函数,在蓝图中重载基类函数时候指向基类函数版本
        (EFunctionFlags)0x04020401, //函数标记
        sizeof(MyClass_eventFunc_Parms),//属性的结构大小
        PropPointers, ARRAY_COUNT(PropPointers),//属性列表
        0,  //RPCId
        0   //RPCResponseId
    };
};

UFunction* Z_Construct_UFunction_UMyClass_Func()
{
    static UFunction* ReturnFunction = nullptr;
    if (!ReturnFunction)
    {   //构造函数
        UE4CodeGen_Private::ConstructUFunction(ReturnFunction, Z_Construct_UFunction_UMyClass_Func_Statics::FuncParams);
    }
    return ReturnFunction;
}

//其他函数...
static const FClassFunctionLinkInfo FuncInfo[]= //发给ClassParams来构造UClass*
{
    { &Z_Construct_UFunction_UMyClass_Func, "Func" }, // 2606493682
    { &Z_Construct_UFunction_UMyClass_ImplementableFunc, "ImplementableFunc" }, // 3752866266
    { &Z_Construct_UFunction_UMyClass_NativeFunc, "NativeFunc" }, // 3036938731
}; 
//接口函数...
void IMyInterface::Execute_ImplementableInterfaceFunc(UObject* O)
{   //通过名字查找函数
    UFunction* const Func = O->FindFunction("ImplementableInterfaceFunc");
    if (Func)
    {
        O->ProcessEvent(Func, NULL);
    }//找不到,其实不会报错,所以是在尝试调用一个接口函数
}
void IMyInterface::Execute_NativeInterfaceFunc(UObject* O)
{   //通过名字查找函数
    UFunction* const Func = O->FindFunction("NativeInterfaceFunc");
    if (Func)
    {
        O->ProcessEvent(Func, NULL);
    }
    else if (auto I = (IMyInterface*)(O->GetNativeInterfaceAddress(UMyInterface::StaticClass())))
    {   //如果找不到蓝图中的版本,则会尝试调用C++里的_Implementation默认实现。
        I->NativeInterfaceFunc_Implementation();
    }
}

There is a lot of code, but the structure is actually very simple. The parameters and return values ​​of the function are packaged into a structure (MyClass_eventFunc_Parms) so that UProperty can have an Offset host. But what's interesting is that in fact, we only need its size for this structure (MyClass_eventFunc_Parms), and we don't need its actual type definition, because we only need to use it to allocate a memory with a consistent memory layout.

We can see that UHT generates default implementations of some functions for us, such as ImplementableFunc and NativeFunc, as well as functions in interfaces, so we should not repeat the implementation in C++ (an error will be reported if we write it). Their internal implementation also calls the version in the blueprint through ProcessEvent, or calls the _Implementation default version in C++. The process of starting the construction is as follows:

void ConstructUFunction(UFunction*& OutFunction, const FFunctionParams& Params)
    {
        UObject*   Outer = Params.OuterFunc ? Params.OuterFunc() : nullptr;
        UFunction* Super = Params.SuperFunc ? Params.SuperFunc() : nullptr;

        if (OutFunction) {return;}

        if (Params.FunctionFlags & FUNC_Delegate)   //生成委托函数
        {
            OutFunction = new (EC_InternalUseOnlyConstructor, Outer, UTF8_TO_TCHAR(Params.NameUTF8), Params.ObjectFlags) UDelegateFunction(
                FObjectInitializer(),
                Super,
                Params.FunctionFlags,
                Params.StructureSize
            );
        }
        else
        {
            OutFunction = new (EC_InternalUseOnlyConstructor, Outer, UTF8_TO_TCHAR(Params.NameUTF8), Params.ObjectFlags) UFunction(
                FObjectInitializer(),
                Super,
                Params.FunctionFlags,
                Params.StructureSize
            );
        }

        ConstructUProperties(OutFunction, Params.PropertyArray, Params.NumProperties);//生成属性列表
        OutFunction->Bind();//函数绑定
        OutFunction->StaticLink();//函数链接
    }

The old routine of construction is to generate dependencies, then new the object, UFunction generates a UProperty list as parameters and return values, and finally Bind and Link in sequence. The SuperFunc of one of the functions actually expresses the function that is inherited and overloaded from the base class. It actually points to the function version in the base class. But in C++, it is always nullptr. Because C++ inherits a UFUNCION, subclasses are not allowed to repeatedly add UFUNCION tags to avoid inconsistent tags in UFUNCION. However, because it is virtual, it can actually be polymorphic normally. So when UFunction* GetSuperFunction()is it valuable? The answer is that when used in a blueprint, all functions in the blueprint are actually UFunctions, so when inheriting and overloading, the SuperFunction of the subclass function will point to the base class version. If we create a new BPMyActor that inherits from AActor, 4 overloaded functions will appear by default: ReceiveBeginPlay、ReceiveTick、ReceiveActorBeginOverlap、UserConstructionScript, their Outer is actually the base class version of the same name UFunction* (the others are external UStruct or UPacakge). It is precisely because of this that UE knows that this function is overloaded, so right-click on the blueprint node to see "AddParentCall".

Binding link

In fact, after constructing various types of objects, you still need to sort them out again and complete some post-initialization work. It is a bit similar to the compilation mechanism of C++. The last step is to link and replace it by locating the symbol to the function address. The same is true in UE, which requires such a binding link operation. I have skipped the discussion of Bind and StaticLink before, now I will explain the internal operations.

Bind

The role of binding is to bind the function pointer to the correct address!

Bind is actually a method defined in UField: virtual void Bind(){}, which means that all fields may need to overload such a binding operation, but in fact only UFunction and UClass have these two operations.

The purpose of UFunction::Bind() is to FNativeFuncPtr Funcbind it to the correct function pointer.

void UFunction::Bind()
{
    UClass* OwnerClass = GetOwnerClass();
    if (!HasAnyFunctionFlags(FUNC_Native))
    {
        Func = &UObject::ProcessInternal;   //非native函数指向蓝图调用
    }
    else
    {
        FName Name = GetFName();    //在之前注册的naive函数表里去查找函数指针
        FNativeFunctionLookup* Found = OwnerClass->NativeFunctionLookupTable.FindByPredicate([=](const FNativeFunctionLookup& NativeFunctionLookup){ return Name == NativeFunctionLookup.Name; });
        if (Found)
        {
            Func = Found->Pointer;  //定位到c++代码里的函数指针。
        }
    }
}

Bind of UClass only needs to be called when compiling the blueprint and loading the class in the Package, because the native class has already passed the function pointer in the previous GetPrivateStaticClassBody. Only classes without C++ code entities need to be bound to the constructor in the base class in order to correctly inherit these functions and call them.

void UClass::Bind()
{
    UStruct::Bind();
    UClass* SuperClass = GetSuperClass();
    if (SuperClass && 
            (ClassConstructor == nullptr || 
            ClassAddReferencedObjects == nullptr || 
            ClassVTableHelperCtorCaller == nullptr)
        )
    {
        SuperClass->Bind();//确保基类已经绑定
        if (!ClassConstructor)
        {
            ClassConstructor = SuperClass->ClassConstructor;//绑定构造函数指针
        }
        if (!ClassVTableHelperCtorCaller)
        {
            ClassVTableHelperCtorCaller = SuperClass->ClassVTableHelperCtorCaller;//绑定热载函数指针
        }
        if (!ClassAddReferencedObjects)
        {
            ClassAddReferencedObjects = SuperClass->ClassAddReferencedObjects;//绑定ARO函数指针
        }

        ClassCastFlags |= SuperClass->ClassCastFlags;
    }
}

The three bound functions are the same as those passed in GetPrivateStaticClassBody.

Link

In the last step of constructing UScriptStuct and UClass, StaicLink is called. It is actually a method of UStruct, which wraps an empty serialized archive class object and forwards it to the function UStruct::Link.

void UStruct::StaticLink(bool bRelinkExistingProperties /*= false*/)
{
    FArchive ArDummy;   //一个空的序列化归档类
    Link(ArDummy, bRelinkExistingProperties);
}

AndUStruct::Link it is a virtual function, which is overloaded in many subclasses. StaicLink is also called in many places. The word Link actually has three meanings:

  1. Just like the compiler's Link, the last operation after compilation is linking, replacing symbol addresses, etc. Typically it is re-linked after structural changes or compilation.
  2. Divide the subfields into chains according to attribute characteristics, such as RefLink.
  3. There is also the concept of Link during serialization, which is used to serve as a link bridge between objects on disk and in memory. Similarly, after a type saved on disk is serialized, it needs to be linked again to reset the attribute offset, structure memory alignment, etc. This is why Link requires a FArchive parameter.

There is actually a Link in UProperty, which is divided into LinkInternal and SetupOffset. LinkInternal is mainly used to set the PropertyFlags according to the attribute characteristics, while SetupOffset is used to set the attribute memory offset after serialization. This part is relatively scattered, so readers are invited to check it out by themselves. The key point is UStruct::Link:

void UStruct::Link(FArchive& Ar, bool bRelinkExistingProperties)
{
    for (UField* Field=Children; (Field != NULL) && (Field->GetOuter() == this); Field = Field->Next)
    {
        if (UProperty* Property = dynamic_cast<UProperty*>(Field))
        {
            Property->LinkWithoutChangingOffset(Ar);//对所有属性先Link一下。
        }
    }
    UProperty** PropertyLinkPtr = &PropertyLink;
    UProperty** DestructorLinkPtr = &DestructorLink;
    UProperty** RefLinkPtr = (UProperty**)&RefLink;
    UProperty** PostConstructLinkPtr = &PostConstructLink;
    TArray<const UStructProperty*> EncounteredStructProps;
    for (TFieldIterator<UProperty> It(this); It; ++It)  //遍历出所有属性
    {
        UProperty* Property = *It;
        if (Property->ContainsObjectReference(EncounteredStructProps) || Property->ContainsWeakObjectReference())
        {
            *RefLinkPtr = Property;//包含对象引用的属性
            RefLinkPtr = &(*RefLinkPtr)->NextRef;
        }
        const UClass* OwnerClass = Property->GetOwnerClass();
        bool bOwnedByNativeClass = OwnerClass && OwnerClass->HasAnyClassFlags(CLASS_Native | CLASS_Intrinsic);
        if (!Property->HasAnyPropertyFlags(CPF_IsPlainOldData | CPF_NoDestructor) &&
            !bOwnedByNativeClass) // these would be covered by the native destructor
        {   
            *DestructorLinkPtr = Property;//需要额外析构的属性
            DestructorLinkPtr = &(*DestructorLinkPtr)->DestructorLinkNext;
        }
        if (OwnerClass && (!bOwnedByNativeClass || (Property->HasAnyPropertyFlags(CPF_Config) && !OwnerClass->HasAnyClassFlags(CLASS_PerObjectConfig))))
        {
            *PostConstructLinkPtr = Property;//需要从CDO中获取初始值的属性
            PostConstructLinkPtr = &(*PostConstructLinkPtr)->PostConstructLinkNext;
        }
        *PropertyLinkPtr = Property;//所有属性
        PropertyLinkPtr = &(*PropertyLinkPtr)->PropertyLinkNext;
    }
    *PropertyLinkPtr = nullptr;
    *DestructorLinkPtr = nullptr;
    *RefLinkPtr = nullptr;
    *PostConstructLinkPtr = nullptr;
}

It looks a bit messy, with various mark judgments, but in fact it is just adding the previous AddCppProperty to the fields in UField* Children, extracting the UProperties, and then stringing them into four chains:

  1. PropertyLink: all properties
  2. RefLink: Contains attributes of object references (UObject*). These attributes have an impact on GC, so they are classified separately to speed up analysis.
  3. PostConstructLink: All attributes that need to obtain initial values ​​from CDO. Attributes can obtain initial values ​​from the Config file or CDO, so the values ​​of the attributes need to be initialized after serialization.
  4. DestructorLink: Attributes that require additional destruction. When destructing, you need to call the attribute's destructor. Otherwise, such as an int attribute, just leave it alone and release the memory.

These four chains are separately classified to speed up performance in different application scenarios, without the need to traverse all attributes every time. UFunction itself is also a UStruct, and its Link will later call InitializeDerivedMembers to calculate the information offset of parameters and return values.

Summarize

It's too long...but there's no other way. It's hard to separate them without stringing them all together in one go. I know that when reading an article, I don’t like to see large sections of code posted. But if readers want to have a deeper understanding of the organizational form of the type system, please refer to the source code of UE to deepen your understanding bit by bit. Let’s sort it out finally:

Type system structure:

The entire type system structure is quite clear. UMetaData has been omitted before. In fact, it collects the information in the macro tags and associates it with the object for use in the editor. UUserDefinedEnum, UUserDefinedStruct and UBlueprintGeneratedClass are all enumeration structures and classes defined and compiled in blueprints. UDynamicClass is a class generated after the blueprint is Nativeized, and FClass is a class generated by the UHT analysis process.

There are various types of attributes, and they all bloom:

I am also dedicated and have listed all the attributes. In order to reuse interfaces as much as possible among so many attributes, UE uses multiple inheritance. The implementation of each attribute is quite simple. Interested readers can take a look at the source code. We will also cover some attributes in the GC chapter later.

The type system is now complete. We will summarize it in the next article.

Guess you like

Origin blog.csdn.net/ttod/article/details/133265791