UE4反射原理的探究

UE4反射

本文主要是个人对UE4反射系统的一些总结和理解。

1. UE4反射系统

什么是反射系统

在UE4里面,你无时无刻都会看到类似UFUNCTION()这样的宏。官方文档告诉你,只要在一个函数的前面加上这个宏,然后在括号里面加上BlueprintCallable就可以在编辑器里面调用了。按照他的指示,我们就能让我们的函数实现各种各样特别的功能,那这个效果就是通过UE4的反射系统来实现的。这看起来确实非常棒,不过同时给UE4的反射系统增添了一点神秘感。我们可能一开始尝试着去找一下这个宏的定义,但是翻了几层发现没有头绪,可能也就懒得再去研究他是怎么实现的了。

其实,所谓反射,是程序在运行时进行自检的一种能力,自检什么呢?我认为就是检查自己的C++类,函数,成员变量,结构体等等(对应起来也就是大家在UE4能看到的UCLASS,UFUNCTON,UPROPERTY,USTRUCT后面还会提到)。

那检查这些东西做什么呢?最明显的就是支持蓝图和C++的交互功能,说的更通俗一点,就是可以更自由的控制这些结构,让他在我们想出现的地方出现,让他在我们想使用的地方使用。要知道我们在虚幻4中声明的任意一个类,都是继承于UObject类的,所以他远远不是我们所以为的那个普通的C++类。我们可以使用这个类进行网络复制,执行垃圾回收,让他和蓝图交互等等。而这一切原生的C++是并不支持的,也正是因此虚幻4才构建了一个这样的反射系统。

反射一般用于哪些地方

#define UPROPERTY(...)
#define UFUNCTION(...)
#define USTRUCT(...)
#define UMETA(...)
#define UPARAM(...)
#define UENUM(...)
#define UDELEGATE(...)
#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
#define UINTERFACE(...) UCLASS()

在UE4里面, 基本上所有的游戏工程的类都需要用到。比如,你用编辑器新建一个类,类的前面会自动添加UCLASS();新建一个结构体,需要使用USTRUCT();新建一个枚举变量,需要在前面声明UENUM();在类的里面,也必须要加上GENERATED_UCLASS_BODY()才行。当你添加这些宏的时候,UHT就会帮你生成反射机制。

如果你想让你的变量能显示在编辑器里面,想让你的函数可以被蓝图调用或者通过让这个函数实现RPC网络通信功能,或者你想让你的变量被系统自动的回收,这些都离不开反射系统以及这些宏定义。

所以,我们这里起码能认识到,在网络通信,蓝图交互以及垃圾回收方面,这与反射系统是密不可分的。

另外,如果要说引擎中哪部分使用到反射系统功能的话,那基本上整个引擎都脱不了干系了。

2. 反射实现机制和基本原理

在了解反射系统前,我们必须要知道两个UE4独特的文件类型—“.generate.h”以及“.generate.cpp”。“.generate.h”文件在每一个类的声明前面都会被包含到对应的头文件里面。(这也是官方建议我们要用编辑器来创建类的原因,他们并不是常规的C++类)而“.generate.cpp”对于一整个项目只会有一个。这两种文件可以说是反射系统的关键所在,他们是通过Unreal Build Tool(UBT)UnrealHeaderTool(UHT)来生成的。

2.1 UBT 和UHT

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)

思考:UHT如何实现反射?

打个比方:UClass其实就好比一张表,一张户口本的东西,指向”真实家庭“的指针。上面记录着一些信息,好比:

张三:

1995年出生

李四:

1991年出生

那虚幻引擎是如何实现这个机制的呢?一种方法是,一开始编译的时候,把表格都填好,放到一个文件处,要找某家人的时候再取出来,但是有一个问题就是,每编译一次的时候,函数地址会发生变化,所以直接存储函数地址这种方法不行。第二种方法就是,存储”进行查找户口调查的过程”,比方说,它存储了每个家庭哪些人需要登记信息。然后当运行开始的时候,逐个让每一家人进行登记。而UHT就是在为这个过程提供帮助,它生成的.generate.h 和 .generate.cpp就是存储进行查找户口调查的过程。

2.2 .generate.h 和 .generate.cpp

“.generate.h”与“.generate.cpp”文件里面都是什么?有什么作用?

“.generate.h”里面是宏,而且包含一个非常庞大的宏,这个宏把所有和反射相关的方法(包括定义)和结构体连接到一起。

而“.generate.cpp”里面是许多的函数定义,UHT根据你在头文件里面使用的宏(UFUNCTION等)自动的生成这个文件,所以这个文件并不需要你去修改,也不允许修改。UBT属性通过扫描头文件,记录任何至少有一个反射类型的头文件的模块。如果其中任意一个头文件从上一次编译起发生了变化,那么 UHT就会被调用来利用和更新反射数据。UHT分析头文件,创建一系列反射数据,并且生成包含反射数据的C++代码。

2.3 反射类型和层次结构

官方文档所给出的基本层次结构

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

2.4 热重载


​ 在编辑器模式下,UE4将工程代码编译成动态链接库,这样编辑器可以动态的加载和卸载某个动态链接库。UE4为工程自动生成一个cpp文件(本工程为HelloUE4.generated.cpp),cpp文件包含了当前工程中所有需要反射的类信息,以及类成员列表和每个成员的类型信息。在动态链接库被编辑器加载的时候,自动将类信息注册到编辑器中。反之,卸载的时候,这样类信息也会被反注册。

​ 在开发的过程中,当我们编译完成工程的时候,UE4编辑器会自动检测动态链接库的变化,然后自动热重载这些动态链接库中的类信息。

这部分还不是很熟悉,所以在本文中不对热重载进行探讨。

3. 反射代码实例分析

UE4引擎启动时候,是分Module(dll)来构建类型信息的。Module采用模拟一般程序的构建流程的方法,大致需要以下几个阶段:生成、收集、注册、链接。

生成阶段:借助UHT(Unreal Header Tool)工具,生成UClass代码,包括UClass构造,注册属性和函数等;

收集阶段:利用Static自动注册方式,在模块加载的时候,将所有UClass登记,集中在Array管理;

注册阶段:在模块初始化的时候,将Array中的所有UClass相关的Function和Property注册;

链接阶段:就是反射功能。

生成阶段

要让一个类支持反射,你需要让这个类要继承自UObject、在类声明前添加UCLASS(或USTRUCT)标识,并且include “xxx.generated.h”头文件(而且必须是最后一个include)。

当你启动UE4编译时,UE4会首先运行UHT (UnrealHeaderTool),UHT成功运行后才会执行真正的编译。UHT是一个头文件解析和代码生成工具,它会处理所有的头文件,从中检索UCLASS、GENERATED_BODY、UPROPERTY、UFUNTION等关键字,检索到以后就为它们生成.generate.h 和 .generate.cpp。

以下是创建的一个小小的demo来对反射进行更好的理解

创建一个新的项目,然后创建一个类继承AGamoModeBase。

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "HelloGameMode.generated.h"     // 核心内容,必须放在最后一行,由UBT自动生成。

UCLASS()
class HELLOWORLD_API AHelloGameMode : public AGameModeBase
{
    GENERATED_BODY()                  // 重中之重
protected:

    UPROPERTY(BlueprintReadWrite, Category = "AReflectionStudyGameMode")
        float Score;

    UFUNCTION(BlueprintCallable, Category = "AReflectionStudyGameMode")
        void CallableFuncTest();

    UFUNCTION(BlueprintNativeEvent, Category = "AReflectionStudyGameMode")
        void NavtiveFuncTest();

    UFUNCTION(BlueprintImplementableEvent, Category = "AReflectionStudyGameMode")
        void ImplementableFuncTest();

};

首先UHT帮我自动生成了四个文件。

这里写图片描述

文件路径为:helloworld\Intermediate\Build\Win64\UE4Editor\Inc\helloworld

HelloGameMode.generated.h

UHT分析生成的文件内容如下:由于篇幅原因,只列出一部分代码。

 // ...
 // ...
#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_RPC_WRAPPERS_NO_PURE_DECLS \
    virtual void NavtiveFuncTest_Implementation(); \
 \
    DECLARE_FUNCTION(execNavtiveFuncTest) \
    { \
        P_FINISH; \
        P_NATIVE_BEGIN; \
        P_THIS->NavtiveFuncTest_Implementation(); \
        P_NATIVE_END; \
    } \
 \
    DECLARE_FUNCTION(execCallableFuncTest) \
    { \
        P_FINISH; \
        P_NATIVE_BEGIN; \
        P_THIS->CallableFuncTest(); \
        P_NATIVE_END; \
    }


#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_EVENT_PARMS
#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_CALLBACK_WRAPPERS
#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_INCLASS_NO_PURE_DECLS \
private: \
    static void StaticRegisterNativesAHelloGameMode(); \
    friend struct Z_Construct_UClass_AHelloGameMode_Statics; \
public: \
    DECLARE_CLASS(AHelloGameMode, AGameModeBase, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT("/Script/helloworld"), NO_API) \
    DECLARE_SERIALIZER(AHelloGameMode)


#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS \
    /** Standard constructor, called after all reflected properties have been initialized */ \
    NO_API AHelloGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \
private: \
    /** Private move- and copy-constructors, should never be used */ \
    NO_API AHelloGameMode(AHelloGameMode&&); \
    NO_API AHelloGameMode(const AHelloGameMode&); \
public: \
    DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AHelloGameMode); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AHelloGameMode); \
    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode)


#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_PRIVATE_PROPERTY_OFFSET \
    FORCEINLINE static uint32 __PPO__Score() { return STRUCT_OFFSET(AHelloGameMode, Score); }


#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
    helloworld_Source_helloworld_Public_HelloGameMode_h_15_PRIVATE_PROPERTY_OFFSET \  // 关于UPROPERTY部分,具体还没研究透,看代码像是获取指针地址的偏移值
    helloworld_Source_helloworld_Public_HelloGameMode_h_15_RPC_WRAPPERS_NO_PURE_DECLS \  //
    helloworld_Source_helloworld_Public_HelloGameMode_h_15_CALLBACK_WRAPPERS \  // 空宏
    helloworld_Source_helloworld_Public_HelloGameMode_h_15_INCLASS_NO_PURE_DECLS \
    helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS


#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID helloworld_Source_helloworld_Public_HelloGameMode_h


PRAGMA_ENABLE_DEPRECATION_WARNINGS
GENERATED_BODY

我们在HelloGameMode.cpp中发现,有一个GENERATED_BODY() 宏,该宏是重中之重,其他的UCLASS宏只是提供信息,不参与编译,而GENERATED_BODY正是把声明和元数据定义关联到一起的枢纽。继续查看宏定义。 GENERATED_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)
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY)

而在。generated.h Line63中定义了CURRENT_FILE_ID。所以GENERATED_BODY() 展开就是helloworld_Source_helloworld_Public_HelloGameMode_h_15_GENERATED_BODY,而这个宏的定义则在generated.h Line51。

DECLARE_FUNCTION

通过helloworld_Source_helloworld_Public_HelloGameMode_h_15_GENERATED_BODY向上, 以helloworld_Source_helloworld_Public_HelloGameMode_h_15_RPC_WRAPPERS_NO_PURE_DECLS为例:

#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_RPC_WRAPPERS_NO_PURE_DECLS \
    virtual void NavtiveFuncTest_Implementation(); \
 \
    DECLARE_FUNCTION(execNavtiveFuncTest) \
    { \
        P_FINISH; \
        P_NATIVE_BEGIN; \
        P_THIS->NavtiveFuncTest_Implementation(); \
        P_NATIVE_END; \
    } \
 \
    DECLARE_FUNCTION(execCallableFuncTest) \
    { \
        P_FINISH; \
        P_NATIVE_BEGIN; \
        P_THIS->CallableFuncTest(); \
        P_NATIVE_END; \
    }

// 由于NavtiveFuncTest是BlueprintNativeEvent,在蓝图和C++均可实现,而在C++我们定义的函数名称为NavtiveFuncTest_Implementation();原来是因为UBT在内部已经帮我们实现了这部分的定义。
// DECLARE_FUNCTION(execNavtiveFuncTest),声明一个函数名字为execNavtiveFuncTest,由于蓝图执行的函数的命名规范是exec开头的,当蓝图调用这个函数的时候,则会调用P_THIS->NavtiveFuncTest_Implementation();
  1. 由于NavtiveFuncTest是BlueprintNativeEvent,在蓝图和C++均可实现,而在C++我们定义的函数名称为NavtiveFuncTest_Implementation();原来是因为UBT在内部已经帮我们实现了这部分的定义。

  2. DECLARE_FUNCTION(execNavtiveFuncTest),声明一个函数名字为execNavtiveFuncTest,由于蓝图执行的函数的命名规范是exec开头的,当蓝图调用这个函数的时候,则会调用P_THIS->NavtiveFuncTest_Implementation();

DECLARE_CLASS

helloworld_Source_helloworld_Public_HelloGameMode_h_15_INCLASS_NO_PURE_DECLS部分:

#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_INCLASS_NO_PURE_DECLS \
private: \
    static void StaticRegisterNativesAHelloGameMode(); \    // 在HelloGameMode.gen.cpp中实现
    friend struct Z_Construct_UClass_AHelloGameMode_Statics; \ // 在HelloGameMode.gen.cpp中定义
public: \
    DECLARE_CLASS(AHelloGameMode, AGameModeBase, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT("/Script/helloworld"), NO_API) \
    DECLARE_SERIALIZER(AHelloGameMode)      // 序列化,先忽略

DECLARE_CLASS是最重要的一个声明,对照着定义:DECLARE_CLASS(AHelloGameMode, AGameModeBase, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT("/Script/helloworld"), NO_API)

  • TClass:类名
  • TSuperClass:基类名字
  • TStaticFlags:类的属性标记
  • TStaticCastFlags:指定了该类可以转换为哪些类,这里为0表示不能转为那些默认的类,读者可以自己查看EClassCastFlags声明来查看具体有哪些默认类转换。
  • TPackage:类所处于的包名,所有的对象都必须处于一个包中,而每个包都具有一个名字,可以通过该名字来查找。这里是”/Script/helloworld”,指定是Script下的helloworld,Script可以理解为用户自己的实现,不管是C++还是蓝图,都可以看作是引擎外的一种脚本,当然用这个名字也肯定有UE3时代UnrealScript的影子。Hello就是项目名字,该项目下定义的对象处于该包中。Package的概念涉及到后续Object的组织方式,目前可以简单理解为一个大的Object包含了其他子Object。
  • TRequiredAPI:就是用来Dll导入导出的标记,这里是NO_API1,因为是最终exe,不需要导出。
#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; \
    }
//在这部分中,最重要部分则是我们常用的StaticClass(), 调用了GetPrivateStaticClass(),后续会讲到这个函数在哪里实现。

StaticClass,是我们平时用到的一个获取反射类型的一个函数,原来UHT已经在内部帮我们定义了。这个也是很重要的一个函数。具体后面会讲述。

DefaultConstructor

helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS部分:

#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS \
    /** Standard constructor, called after all reflected properties have been initialized */ \
    NO_API AHelloGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \   //根据注释的意思,就是标准的构造函数,在所有反射的属性初始化后调用。
private: \
    /** Private move- and copy-constructors, should never be used */ \     //永远不要用这两个函数
    NO_API AHelloGameMode(AHelloGameMode&&); \   //移动构造函数
    NO_API AHelloGameMode(const AHelloGameMode&); \   //拷贝构造函数
public: \
    DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AHelloGameMode); \   // 热加载,先忽略
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AHelloGameMode); \    // 空宏
    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode)   // 定义一个构造函数

继续查看DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL的定义:

#define DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(TClass) \
    static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }

声明定义了一个构造函数包装器。需要这么做的原因是,在根据名字反射创建对象的时候,需要调用该类的构造函数。可是类的构造函数并不能用函数指针指向,因此这里就用一个static函数包装一下,变成一个”平凡”的函数指针,而且所有类的签名一致,就可以在UClass里用一个函数指针里保存起来 。

在上述的StaticClass中传递进去作为构造函数。


HelloGameMode.gen.cpp

由于这个类代码太长,所以的话就分版块讨论。

ProcessEvent
static FName NAME_AHelloGameMode_ImplementableFuncTest = FName(TEXT("ImplementableFuncTest"));
void AHelloGameMode::ImplementableFuncTest()
{
    ProcessEvent(FindFunctionChecked(NAME_AHelloGameMode_ImplementableFuncTest),NULL);
}
// 为啥BlueprintImplementableEvent的函数不用我们去实现呢,是因为UBT帮我们自己实现了。而关于ProcessEvent部分, 这个方法在UObject中实现的。
  • 刚接触UE4的时候,如果是BlueprintImplementabeEvent的函数,是不是发现不需要自己去实现,那么当时有没有觉得怪异呢,上面的代码就解释清楚了,那是UE4帮我们实现了,可以看到它调用了ProcessEvent方法,这个方法在UObject中实现的。
  • 而且如果是BlueprintImplementabeEvent或者RPC的那些函数,我们是不需要实现其函数方法的,如果在CPP文件定义实现的话,则会报错,那是因为在.gen.cpp中已经帮我们实现了这个函数。
IMPLEMENT_CLASS
IMPLEMENT_CLASS(AHelloGameMode, 1552540694);
//  in IMPLEMENT_CLASS
// Register a class at startup time.
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,     // StaticRegisterNativesAHelloGameMode,
            sizeof(TClass), 
            (EClassFlags)TClass::StaticClassFlags, 
            TClass::StaticClassCastFlags(), 
            TClass::StaticConfigName(), 
            (UClass::ClassConstructorType)InternalConstructor<TClass>,    //构造函数,在DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode) 实现

            (UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, 
            &TClass::AddReferencedObjects, 
            &TClass::Super::StaticClass, 
            &TClass::WithinClass::StaticClass 
        ); 
    } 
    return PrivateStaticClass; 
}

我们在DECLARE_CLASS中可以看到StaticClass调用了GetPrivateStaticClass,其实就是在IMPLEMENT_CLASS中实现的。这部分在下面收集阶段会再详细讲解。

ConstructClass
static FCompiledInDefer Z_CompiledInDefer_UClass_AHelloGameMode(Z_Construct_UClass_AHelloGameMode, &AHelloGameMode::StaticClass, TEXT("/Script/helloworld"), TEXT("AHelloGameMode"), false, nullptr, nullptr, nullptr);

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

通过声明一个全局变量的形式,来静态自动注册的模式,从而达到收集信息的目的。具体的在收集阶段详细讲解


收集阶段

自动化注册

思考:UE4如何实现自动注册的呢?

主要是通过Static 自动注册的方式

而在本例子是如何通过Static自动注册呢?

回顾生成阶段的IMPLEMENT_CLASSConstructClass中静态声明了两个变量。

#define IMPLEMENT_CLASS(TClass, TClassCrc) \
    static TClassCompiledInDefer<TClass> 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);
  • 这两个全局静态变量在Main函数之前就会初始化

  • 初始化就会调用这两个变量的构造函数

  • 构造函数会分别调用UClassCompiledInDefer函数和UObjectCompiledInDefer函数。

  • 这两个函数会把ClassInfo放进延迟注册的数组中去。

思考:为何需要TClassCompiledInDefer和FCompiledInDefer两个静态初始化来登记?

我们也观察到了这二者是一一对应的,问题是为何需要两个静态对象来分别收集,为何不合二为一?关键在于我们首先要明白它们二者的不同之处,前者的目的主要是为后续提供一个TClass::StaticClass的Register方法(其会触发GetPrivateStaticClassBody的调用,进而创建出UClass*对象),而后者的目的是在其UClass*身上继续调用构造函数,初始化属性和函数等一些注册操作。我们可以简单理解为就像是C++中new对象的两个步骤,首先分配内存,继而在该内存上构造对象。我们在后续的注册章节里还会继续讨论到这个问题。

思考:为啥要采用延迟注册的方式?为什么不在收集阶段直接注册呢?

主要考虑到UE4超多类,都在static初始化阶段注册,程序会表现启动速度慢,用户双击程序,没反应。所以采用延迟注册。

启动过程分析

上面我们讲解了这个注册信息的过程,而它们的执行是伴随着当前模块的加载而执行的,我们都知道静态变量的初始化是先于Main函数执行的。下面我们简单画了一下虚幻编辑器的启动流程,这样我们就可以准确地看到整个注册反射信息的过程了。

void ProcessNewlyLoadedUObjects()
{
    // ...
    UClassRegisterAllCompiledInClasses();

    const TArray<UClass* (*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();
    const TArray<FPendingStructRegistrant>& DeferredCompiledInStructRegistration = GetDeferredCompiledInStructRegistration();
    const TArray<FPendingEnumRegistrant>& DeferredCompiledInEnumRegistration = GetDeferredCompiledInEnumRegistration();

    bool bNewUObjects = false;
    while( GFirstPendingRegistrant || DeferredCompiledInRegistration.Num() || DeferredCompiledInStructRegistration.Num() || DeferredCompiledInEnumRegistration.Num() )
    {
        bNewUObjects = true;
        UObjectProcessRegistrants();
        UObjectLoadAllCompiledInStructs();
        UObjectLoadAllCompiledInDefaultProperties();
    }
// ...
}

在这个函数中,可以看到void ProcessNewlyLoadedUObjects()这个函数就是我们主要关注的函数,我们前面讲到的注册的信息,包括类、结构体以及枚举类型的反射信息都会在这里进行注册。

收集

在生成阶段中,我们生成了很多Class、Property、Function的信息。但是我们需要它们的信息收集整合到我们需要的数据结构里保存,以便下一阶段的使用。

通过UClassCompiledInDefer收集

这里写图片描述

深入UClassCompiledInDefer方法我们可以发现。

void UClassCompiledInDefer(FFieldCompiledInInfo* ClassInfo, const TCHAR* Name, SIZE_T ClassSize, uint32 Crc)
{
    // We will either create a new class or update the static class pointer of the existing one
    GetDeferredClassRegistration().Add(ClassInfo);  //static TArray<FFieldCompiledInInfo*> DeferredClassRegistration;
}

可以看到UClassCompiledInDefer(this, InName, InClassSize, InCrc),传进去4个参数,主要是收集三个数据,为啥要传4个参数呢,是因为把自己的指针传进去,为了方便后续调用Register()方法。

实现的功能主要是在一个静态Array里添加信息记录。

这个数组会在后续注册Class的时候会被调用,然后调用Register(),实现注册功能。

/** Register all loaded classes */
void UClassRegisterAllCompiledInClasses()
{
    //...
    TArray<FFieldCompiledInInfo*>& DeferredClassRegistration = GetDeferredClassRegistration();
    for (const FFieldCompiledInInfo* Class : DeferredClassRegistration)
    {
        UClass* RegisteredClass = Class->Register();     //实现注册,关键步骤。
#if WITH_HOT_RELOAD
        if (GIsHotReload && Class->OldClass == nullptr)
        {
            AddedClasses.Add(RegisteredClass);
        }
#endif
    }
    DeferredClassRegistration.Empty();   //注册完成,清空注册表
    //...
}

而Register函数实际上传进来的就是StaticClass函数

接着就是StaticClass()部分如何收集的解释。

通过StaticClass()我们可以发现主要是调用了GetPrivateStaticClass(),而GetPrivateStaticClass()也主要是调用了GetPrivateStaticClassBody()函数,那么通过这个函数收集到了哪些信息呢。

以下是主要的信息:

**PackageName:**StaticPackage()

Name:类名,+1去掉U、A、F前缀,+11去掉_Deprecated前缀

ReturnClass:输出引用,也就是收集信息后产生的UClass,PrivateStaticClass

void(*RegisterNativeFunc)(): StaticRegisterNativesAHelloGameMode(), 收集原生的函数。

InSize: 类的大小

InClassConstructor:构造函数,在DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode)实现。

InSuperClassFn:&TClass::Super::StaticClass, 父类的注册函数。

//  in DECLARE_CLASS
inline static UClass* StaticClass()
{
    return GetPrivateStaticClass();
}


//  in IMPLEMENT_CLASS
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,     // StaticRegisterNativesAHelloGameMode,
            sizeof(TClass), 
            (EClassFlags)TClass::StaticClassFlags, 
            TClass::StaticClassCastFlags(), 
            TClass::StaticConfigName(), 
            (UClass::ClassConstructorType)InternalConstructor<TClass>,    //构造函数,在DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode) 实现

            (UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, 
            &TClass::AddReferencedObjects, 
            &TClass::Super::StaticClass, 
            &TClass::WithinClass::StaticClass 
        ); 
    } 
    return PrivateStaticClass; 
}

这里主要是通过收集到的信息构造UClass。

主要有三部分:

  1. 首先给ReturnClass分配内存,然后初始化构造。
  2. 首先确保自己的父类已经注册了,设置SuperStruct和 在全局PendingRegistrantsMaps中添加自己身类,延迟注册。(在注册阶段会对这部分进行解释)
  3. Register the class’s native functions,注册自身原生的函数、蓝图可执行的函数,执行 StaticRegisterNativesAHelloGameMode();
// 删减部分代码
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*/
    )
{
    if (!bIsDynamic)
    {
        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
            );
        check(ReturnClass);
    }
    // 设置SuperStruct和 在全局PendingRegistrantsMaps中添加自己身类,后续注册UClass的时候会从Maps去除自身。
    InitializePrivateStaticClass(
        InSuperClassFn(),
        ReturnClass,
        InWithinClassFn(),
        PackageName,
        Name
        );

    // Register the class's native functions.
    // 注册自身原生的函数。蓝图可执行的函数
    // 执行 StaticRegisterNativesAHelloGameMode();
    RegisterNativeFunc();
}

通过UObjectCompiledInDefer收集

这里写图片描述

深入UObjectCompiledInDefer方法我们可以发现:

void UObjectCompiledInDefer(UClass *(*InRegister)(), UClass *(*InStaticClass)(), const TCHAR* Name, const TCHAR* PackageName, bool bDynamic, const TCHAR* DynamicPathName, void (*InInitSearchableValues)(TMap<FName, FName>&))
{
    // ...
    TArray<UClass *(*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();
    checkSlow(!DeferredCompiledInRegistration.Contains(InRegister));
    DeferredCompiledInRegistration.Add(InRegister);
    // ...
}

传进去一个InRegister函数指针到延迟注册数组中,也就是Z_Construct_UClass_AHelloGameMode函数。

然后在启动阶段会调用UObjectLoadAllCompiledInDefaultProperties函数,遍历所有的延迟注册数组,进行收集注册。

static void UObjectLoadAllCompiledInDefaultProperties()
{
    static FName LongEnginePackageName(TEXT("/Script/Engine"));
    TArray<UClass *(*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();
    const bool bHaveRegistrants = DeferredCompiledInRegistration.Num() != 0;
    if( bHaveRegistrants )
    {
        TArray<UClass*> NewClasses;
        TArray<UClass*> NewClassesInCoreUObject;
        TArray<UClass*> NewClassesInEngine;
        TArray<UClass* (*)()> PendingRegistrants = MoveTemp(DeferredCompiledInRegistration);
        for (UClass* (*Registrant)() : PendingRegistrants)
        {
            UClass* Class = Registrant();      // 在这一步调用了Z_Construct_UClass_AHelloGameMode函数
            //...
        }
        // ...
    }
}

而Z_Construct_UClass_AHelloGameMode函数主要是调用了ConstructClass函数。

UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_AHelloGameMode_Statics::ClassParams);

可以看到,Construct函数传进去两个参数,一个OuterClass,一个ClassParams。

OuterClass是我们注册后得到的输出对象。

而ClassParams是我们需要收集的数据。


以下让我们对ClassParams的数据进行一些分析,下面是一些重要的数据部分:

ClassNoRegisterFunc: StaticClass,作用在上面有具体讲述

DependencySingletonFuncArray: 所依赖的构建函数,确保自己所依赖部分都被构建成功。

FunctionLinkArray:需要反射的函数数组

PropertyArray: 属性数组


const UE4CodeGen_Private::FClassParams Z_Construct_UClass_AHelloGameMode_Statics::ClassParams = {
    &AHelloGameMode::StaticClass,
    DependentSingletons, ARRAY_COUNT(DependentSingletons),
    0x009002A8u,
    FuncInfo, ARRAY_COUNT(FuncInfo),
    Z_Construct_UClass_AHelloGameMode_Statics::PropPointers, ARRAY_COUNT(Z_Construct_UClass_AHelloGameMode_Statics::PropPointers),
    nullptr,
    &StaticCppClassTypeInfo,
    nullptr, 0,
    METADATA_PARAMS(Z_Construct_UClass_AHelloGameMode_Statics::Class_MetaDataParams, ARRAY_COUNT(Z_Construct_UClass_AHelloGameMode_Statics::Class_MetaDataParams))
};


struct FClassParams
{
    UClass*                                   (*ClassNoRegisterFunc)();      //  传进来StaticClass的函数
    UObject*                           (*const *DependencySingletonFuncArray)();   // 传进所依赖函数的函数数组
    int32                                       NumDependencySingletons;  //上述函数的个数
    uint32                                      ClassFlags; // EClassFlags
    const FClassFunctionLinkInfo*               FunctionLinkArray;    // 需要反射的函数,具体的后面会讲述
    int32                                       NumFunctions;  //同上
    const FPropertyParamsBase* const*           PropertyArray;   // 后续再讲这个
    int32                                       NumProperties; //同上
    const char*                                 ClassConfigNameUTF8;
    const FCppClassTypeInfoStatic*              CppClassInfo;
    const FImplementedInterfaceParams*          ImplementedInterfaceArray;   //本例子中没用上
    int32                                       NumImplementedInterfaces;   // 0
#if WITH_METADATA
    const FMetaDataPairParam*                   MetaDataArray;
    int32                                       NumMetaData;
#endif
};

以下是ConstructUclass的部分内容。

根据流程图,可以看到,首先是执行的DependecySingletonFunctions,检测自己所依赖的单例是否都建立了反射关系。接着就是StaticClass函数部分,这也是我们重点关注的部分。在StaticClass,实现了构建了一个基础的UClass的反射,但是还没有完善细节,因此在后续部分给NewClass添加细节部分。

步骤:

  1. 执行DependecySingletonFunctions数组中所依赖的函数,确保自身的父类反射已经被构建和自身所在的Package反射被构建。
  2. UObjectForceRegistration,把自己从等待注册的Map中去掉, 并且注册
  3. ConstructUFunction,组建Function,并且注册Function。(这部分后续再探讨
  4. ConstructUProperties,组建Properties。(这部分后续再探讨

下面是ConstructUClass的部分内容,删减了一部分代码。

void ConstructUClass(UClass*& OutClass, const FClassParams& Params)  // 关键点,用了引用
{
    if (OutClass && (OutClass->ClassFlags & CLASS_Constructed))  // 如果已经构建过了,则返回
    {
        return;
    }

    // 执行所依赖过的函数, 需要所依赖部分已经建立反射关系。(PS: Ue4源码部分 充斥着大量的懒汉单例模式)
    for (UObject* (*const *SingletonFunc)() = Params.DependencySingletonFuncArray, *(*const *SingletonFuncEnd)() = SingletonFunc + Params.NumDependencySingletons; SingletonFunc != SingletonFuncEnd; ++SingletonFunc)
    {
        (*SingletonFunc)();
    }
    // 所传进来的是StaticClass函数
    UClass* NewClass = Params.ClassNoRegisterFunc();   
    OutClass = NewClass;
    if (NewClass->ClassFlags & CLASS_Constructed)  // 如果这个已经构建过了,返回
    {
        return;
    }

    // 延迟注册 
    // 把自己从等待注册的Map中去掉
    UObjectForceRegistration(NewClass);    
    NewClass->ClassFlags |= (EClassFlags)(Params.ClassFlags | CLASS_Constructed);
    // Make sure the reference token stream is empty since it will be reconstructed later on
    // This should not apply to intrinsic classes since they emit native references before AssembleReferenceTokenStream is called.
    if ((NewClass->ClassFlags & CLASS_Intrinsic) != CLASS_Intrinsic)
    {
        check((NewClass->ClassFlags & CLASS_TokenStreamAssembled) != CLASS_TokenStreamAssembled);
        NewClass->ReferenceTokenStream.Empty();
#if ENABLE_GC_OBJECT_CHECKS
        NewClass->DebugTokenMap.Empty();
#endif
    }

    // 创建Function的反射,利用传进来的构建函数来构建函数。
    NewClass->CreateLinkAndAddChildFunctionsToMap(Params.FunctionLinkArray, Params.NumFunctions);

    // 如果看过上一篇blog的话,就可以发现其实属性构建部分是没有单独的函数构建的
    // 原来在这部分实现。
    ConstructUProperties(NewClass, Params.PropertyArray, Params.NumProperties);

    // ConstructClass还有很多内容,但是由于篇幅关系,这里就不一一放出来。
}

注册阶段

这里写图片描述

注册阶段主要分为两个过程,首先构建UClass的时候添加进等待注册表,等待注册,在收集完成UClass所有信息的时候,延迟注册。

添加进等待注册表,等待注册

在InitializePrivateStaticClass函数中执行Register函数,实现添加进等待注册表,等待注册过程。

COREUOBJECT_API void InitializePrivateStaticClass(
    class UClass* TClass_Super_StaticClass,
    class UClass* TClass_PrivateStaticClass,
    class UClass* TClass_WithinClass_StaticClass,
    const TCHAR* PackageName,
    const TCHAR* Name
    )
{   
    // ...
    // Defer
    TClass_PrivateStaticClass->Register(PackageName, Name);
    // ...
}


/** Enqueue the registration for this object. */
void UObjectBase::Register(const TCHAR* PackageName,const TCHAR* InName)
{
    TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap();

    FPendingRegistrant* PendingRegistration = new FPendingRegistrant(this);
    PendingRegistrants.Add(this, FPendingRegistrantInfo(InName, PackageName));
    if(GLastPendingRegistrant)
    {
        GLastPendingRegistrant->NextAutoRegister = PendingRegistration;
    }
    else
    {
        check(!GFirstPendingRegistrant);
        GFirstPendingRegistrant = PendingRegistration;
    }
    GLastPendingRegistrant = PendingRegistration;
}
收集信息完成,延迟注册

现在,已经进入了等待注册表内,而当我们收集完所有的UClass的信息的时候,我们注册UClass。

void UObjectForceRegistration(UObjectBase* Object)
{
    TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap();

    FPendingRegistrantInfo* Info = PendingRegistrants.Find(Object);
    if (Info)
    {
        const TCHAR* PackageName = Info->PackageName;
        const TCHAR* Name = Info->Name;
        PendingRegistrants.Remove(Object);  // delete this first so that it doesn't try to do it twice
        Object->DeferredRegister(UClass::StaticClass(),PackageName,Name);
    }
}
注册过程分析

Ue4是如何完成注册的呢?

/**
 * Convert a boot-strap registered class into a real one, add to uobject array, etc
 *
 * @param UClassStaticClass Now that it is known, fill in UClass::StaticClass() as the class
 */
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());
}

其实就是把UClass放到一个Hash表中去。

void HashObject(UObjectBase* Object)
{
    auto& ThreadHash = FUObjectHashTables::Get();
    Hash = GetObjectHash(Name);                 
    ThreadHash.AddToHash(Hash, Object);
    Hash = GetObjectOuterHash( Name, (PTRINT)Object->GetOuter() );
    ThreadHash.HashOuter.Add( Hash, Object );
    AddToOuterMap( ThreadHash, Object );
    AddToClassMap( ThreadHash, Object );
}

通过上面的代码,我们可以发现通过名字生成Index和通过OuterName(也就是PackageName)来生成Index,然后添加进Map内部中。具体如何添加进HashMap中呢?请看下面的分析。

TUObjectHashTables

在这里就不得不介绍这个类TUObjectHashTables了,主要的存储的类。注册的反射的数据都以存放在这里。

首先看看这个类的定义

class FUObjectHashTables
{
    FCriticalSection CriticalSection;

public:

    /** Hash sets */
    TMap<int32, FHashBucket> Hash;
    TMultiMap<int32, class UObjectBase*> HashOuter;

    /** Map of object to their outers, used to avoid an object iterator to find such things. **/
    TMap<UObjectBase*, FHashBucket> ObjectOuterMap;
    TMap<UClass*, TSet<UObjectBase*> > ClassToObjectListMap;
    TMap<UClass*, TSet<UClass*> > ClassToChildListMap;

    static FUObjectHashTables& Get()   // 饿汉单例
    {
        static FUObjectHashTables Singleton;
        return Singleton;
    }

    FORCEINLINE void AddToHash(int32 InHash, UObjectBase* Object)
    {
        FHashBucket& Bucket = Hash.FindOrAdd(InHash);
        Bucket.Add(Object);
    }
}

首先这个类用了饿汉单例的设计模式,保证只有一个存储数据的实例,

从这个类中我们可以看到有5个Map,具体的映射方式也都显而易见了,看看我们注册的时候用了这几个Map是怎样处理的。

void HashObject(UObjectBase* Object)
{
    auto& ThreadHash = FUObjectHashTables::Get();    //获取存储单例
    Hash = GetObjectHash(Name);     //把FName转化为Int Index,来映射            
    ThreadHash.AddToHash(Hash, Object);
    Hash = GetObjectOuterHash( Name, (PTRINT)Object->GetOuter() );
    ThreadHash.HashOuter.Add( Hash, Object );
    AddToOuterMap( ThreadHash, Object );
    AddToClassMap( ThreadHash, Object );
}

第一个Map:Hash,TMap<int32, FHashBucket> Hash,映射方式通过int32映射到一个桶。(每个桶我们其实都可以看成是一个类的集合)(为了容易理解,此处的int32亦可以理解为FName)

ThreadHash.AddToHash(Hash, Object);

FORCEINLINE void AddToHash(int32 InHash, UObjectBase* Object)
{
    FHashBucket& Bucket = Hash.FindOrAdd(InHash);
    Bucket.Add(Object);
}

通过代码可以看出来,通过自己的名字找到了属于自己的那个桶,并且在桶中把自己添加进去。

由此可以看出HashMap的用处其实就是通过自身名字找到属于自己的桶集合。


第二个Map:HashOuter,TMultiMap<int32, class UObjectBase*> HashOuter,映射方式通过int32映射到一个UObjectBase类。(为了容易理解,此处的int32亦可以理解为OuterFName)

ThreadHash.HashOuter.Add( Hash, Object );

通过代码可以看出,HashOuterMap的用处其实是通过OuterFName的名字来找到自己的Outer。


第三个Map:ObjectOuterMap,TMap<UObjectBase*, FHashBucket> ObjectOuterMap,映射方式是用过Outer来找到自己的Bucket。

AddToOuterMap( ThreadHash, Object );

// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void AddToOuterMap(FUObjectHashTables& ThreadHash, UObjectBase* Object)
{
    FHashBucket& Bucket = ThreadHash.ObjectOuterMap.FindOrAdd(Object->GetOuter());
    checkSlow(!Bucket.Contains(Object)); // if it already exists, something is wrong with the external code
    Bucket.Add(Object);
}

通过代码可以看出, ObjectOuterMap是通过Outer来获取到Outer的Bucket。(Outer实际上就是Package)(类似于第二个Map)


第四个Map:ClassToObjectListMap,TMap<UClass*, TSet<UObjectBase*> > ClassToObjectListMap

{
    check(Object->GetClass());
    TSet<UObjectBase*>& ObjectList = ThreadHash.ClassToObjectListMap.FindOrAdd(Object->GetClass());
    bool bIsAlreadyInSetPtr = false;
    ObjectList.Add(Object, &bIsAlreadyInSetPtr);
    check(!bIsAlreadyInSetPtr); // if it already exists, something is wrong with the external code  
}

通过代码可以看出, ClassToObjectListMap是通过自身来获取到自身所包含的一个Set(类似于第一个Map)。


第五个Map:ClassToChildListMap,TMap<UClass*, TSet<UClass*> > ClassToChildListMap

UObjectBaseUtility* ObjectWithUtility = static_cast<UObjectBaseUtility*>(Object);
if ( ObjectWithUtility->IsA(UClass::StaticClass()) )
{
    UClass* Class = static_cast<UClass*>(ObjectWithUtility);
    UClass* SuperClass = Class->GetSuperClass();
    if ( SuperClass )
    {
        TSet<UClass*>& ChildList = ThreadHash.ClassToChildListMap.FindOrAdd(SuperClass);
        bool bIsAlreadyInSetPtr = false;
        ChildList.Add(Class, &bIsAlreadyInSetPtr);
        check(!bIsAlreadyInSetPtr); // if it already exists, something is wrong with the external code
    }
}

通过代码我们可以看出这个是通过继承关系来映射。


总结

目前只针对UCLASS部分的分析,而且由于笔者对UE4确实也还不太了解,只能作比较表面的理解,所以其中可能有说的不准确的地方,如果有错误的地方,还请指正 。

UE4版本 4.20

参考:

insideUe4

虚幻4属性系统(反射)翻译 By 风恋残雪


  1. 这是一个空宏。
  • 28
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值