菜单

《InsideUE4》UObject(五)类型系统信息收集

2018年11月13日 - 注册免费送38元体验金

在一起!在一起!

引言

前文中我们阐述了色系统构建的率先只号:生成。UHT分析源码的宏标记并生成了含有程序元信息的代码,继而编译进程序,在先后启动之时段,开始起步项目系统的连续构建等。而本文我们以介绍类型信息的募集等。

C++ Static 自动注册模式

其它一样种常用之C++常用的设计模式:Static Auto
Register。典型的,当您想只要在次启动后为一个容器里登记一些靶,或者簿记一些信息的早晚,一种直接的方式是在先后启动后手动的一个个调用注册函数:

#include "ClassA.h"
#include "ClassB.h"
int main()
{
    ClassFactory::Get().Register<ClassA>();
    ClassFactory::Get().Register<ClassB>();
    [...]
}

这种艺术的短处是您不能不手动的一个include之后再次手动的一个个注册,当要继承丰富注册的宗时,只能更手动的次第序在该文件里增长一条条目,可维护性较差。
据此冲C++
static对象见面以main函数之前初始化的性状,可以计划有同种植static自动注册模式,新长注册条目的时刻,只要Include进相应的类.h.cpp文件,就好自动在程序启动main函数前自行执行有操作。简化的代码大概如下:

//StaticAutoRegister.h
template<typename TClass>
struct StaticAutoRegister
{
    StaticAutoRegister()
    {
        Register(TClass::StaticClass());
    }
};
//MyClass.h
class MyClass
{
    //[...]
};
//MyClass.cpp
#include "StaticAutoRegister.h"
const static StaticAutoRegister<MyClass> AutoRegister;

这般,在次启动之当儿就会执行Register(MyClass),把为新增补加类而生的改观行为限制于了新文件本身,对于有挨家挨户无关的报行为这种模式更加适宜。利用这static初始化特性,也发出那么些单变种,比如您可以将StaticAutoRegister声明进MyClass的一个静态成员变量也堪。不过注意的是,这种模式只能在单独的地点空间才会管用,如果该文件于静态链接且没有为引述到之说话虽然大可能会见绕了static的初始化。不过UE因为都是dll动态链接,且没有出现静态lib再引用Lib,然后以非引用文件的景象出现,所以避免了拖欠问题。或者你为堪找个地方强制的失去include一下来触发static初始化。

UE Static 自动注册模式

使UE里同样是使这种模式:

template <typename TClass>
struct TClassCompiledInDefer : public FFieldCompiledInInfo
{
    TClassCompiledInDefer(const TCHAR* InName, SIZE_T InClassSize, uint32 InCrc)
    : FFieldCompiledInInfo(InClassSize, InCrc)
    {
        UClassCompiledInDefer(this, InName, InClassSize, InCrc);
    }
    virtual UClass* Register() const override
    {
        return TClass::StaticClass();
    }
};

static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); 

或者

struct FCompiledInDefer
{
    FCompiledInDefer(class UClass *(*InRegister)(), class UClass *(*InStaticClass)(), const TCHAR* Name, bool bDynamic, const TCHAR* DynamicPackageName = nullptr, const TCHAR* DynamicPathName = nullptr, void (*InInitSearchableValues)(TMap<FName, FName>&) = nullptr)
    {
        if (bDynamic)
        {
            GetConvertedDynamicPackageNameToTypeName().Add(FName(DynamicPackageName), FName(Name));
        }
        UObjectCompiledInDefer(InRegister, InStaticClass, Name, bDynamic, DynamicPathName, InInitSearchableValues);
    }
};
static FCompiledInDefer Z_CompiledInDefer_UClass_UMyClass(Z_Construct_UClass_UMyClass, &UMyClass::StaticClass, TEXT("UMyClass"), false, nullptr, nullptr, nullptr);

还是针对性该模式之采取,把static变量声明又用宏包装一层,就好兑现一个大概的机关注册流程了。

收集

当上文里,我们详细介绍了Class、Struct、Enum、Interface的代码生成的消息,显然的,生成的就算是以将过来用之。但是在用前,我们就还得辛苦一番,把乱分布在各个个.h.cpp文件里之首批数据都收集及我们怀念如果的数据结构里保存,以便下一个等的利用。

此处回顾一下,为了为初创办的好像非修改既有的代码,所以我们选择了错过中心化的为每个新的类生成它和谐之cpp生成文件——上文里已分别介绍每个cpp文件之始末。但是如此咱们虽随即迎来了一个新题材:这些cpp文件里之状元数据错乱在逐个模块dll里,我们用因此相同种植方式重复联合这些多少,这便是咱于平等开就涉及的C++
Static自动注册模式了。通过这种模式,每个cpp文件里之static对象在先后一样开始的时候就是见面所有来机会错过开片事情,包括信息之征集工作。

UE4里呢是这般,在次启动的时刻,UE利用了Static自动注册模式将所有类的信息还逐项登记一全副。而随后另一个虽是各个问题了,这么多类,谁先谁后,互相若是有依靠该怎么解决。众所周知,UE是盖Module来组织引擎结构的(关于Module的细节会在之后章节叙述),一个个Module可以经脚本配置来选择性的编译加载。在耍引擎众多底模块中,玩家自己的Game模块是处于较高档的层次的,都是据让引擎其他还基础底层的模块,而这些模块中,最极致底部的虽是Core模块(C++的基础库),接着就是CoreUObject,正是落实Object类型系统的模块!因此于路系统登记的经过遭到,不止要报玩家的Game模块,同时为只要登记CoreUObject本身的一些支撑类。

广大人或者会见担心这样多模块的静态初始化的逐一是如何保管,在c++标准里,不同编译单元的大局静态变量的初始化顺序并无明确规定,因此实现达标全出于编译器自己控制。该问题太好之解决方式是拼命三郎的避免这种状况,在筹划及即让各个变量不互相引用依赖,同时为应用局部亚不好检测的方式避免双重登记,或者触发一个强制引用来保管前置对象就给初始化完成。目前于MSVC平台上是优先注册玩家的Game模块,接着是CoreUObject,接着又其他,不过就事实上无所谓的,只要保证不依赖顺序而结果正确,顺序就连无重大了。

Static的收集

于讲话了了采访的必要性与顺序问题之化解以后,我们还来分别的关押各个档次的构造的音信的采。依然是据上文生成的依次,从Class(Interface同理)开始,然后是Enum,接着Struct。接着要读者对象等相比着上文的变动代码来掌握。

Class的收集

对待着上文里的Hello.generated.cpp展开,我们注意到中有:

static TClassCompiledInDefer<UMyClass> AutoInitializeUMyClass(TEXT("UMyClass"), sizeof(UMyClass), 899540749);
//……
static FCompiledInDefer Z_CompiledInDefer_UClass_UMyClass(Z_Construct_UClass_UMyClass, &UMyClass::StaticClass, TEXT("UMyClass"), false, nullptr, nullptr, nullptr);

重同次等找到那定义:

//Specialized version of the deferred class registration structure.
template <typename TClass>
struct TClassCompiledInDefer : public FFieldCompiledInInfo
{
    TClassCompiledInDefer(const TCHAR* InName, SIZE_T InClassSize, uint32 InCrc)
    : FFieldCompiledInInfo(InClassSize, InCrc)
    {
        UClassCompiledInDefer(this, InName, InClassSize, InCrc);    //收集信息
    }
    virtual UClass* Register() const override
    {
        return TClass::StaticClass();
    }
};

//Stashes the singleton function that builds a compiled in class. Later, this is executed.
struct FCompiledInDefer
{
    FCompiledInDefer(class UClass *(*InRegister)(), class UClass *(*InStaticClass)(), const TCHAR* Name, bool bDynamic, const TCHAR* DynamicPackageName = nullptr, const TCHAR* DynamicPathName = nullptr, void (*InInitSearchableValues)(TMap<FName, FName>&) = nullptr)
    {
        if (bDynamic)
        {
            GetConvertedDynamicPackageNameToTypeName().Add(FName(DynamicPackageName), FName(Name));
        }
        UObjectCompiledInDefer(InRegister, InStaticClass, Name, bDynamic, DynamicPathName, InInitSearchableValues);//收集信息
    }
};

好望前者调用了UClassCompiledInDefer来收集类名字,类大小,CRC信息,并拿好的指针保存进来以便后续调用Register方法。而UObjectCompiledInDefer(现在临时无考虑动态类)最根本之集之音信就是是首先单用于组织UClass*目标的函数指针回调。

重为生我们会发觉立即两头其实还只是在一个静态Array里添加信息记录:

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;
}
void UObjectCompiledInDefer(UClass *(*InRegister)(), UClass *(*InStaticClass)(), const TCHAR* Name, bool bDynamic, const TCHAR* DynamicPathName, void (*InInitSearchableValues)(TMap<FName, FName>&))
{
    //...
    GetDeferredCompiledInRegistration().Add(InRegister);    //static TArray<class UClass *(*)()> DeferredCompiledInRegistration;
}

假若当周引擎里会触发此Class的音讯搜集之发出UCLASS、UINTERFACE、IMPLEMENT_INTRINSIC_CLASS、IMPLEMENT_CORE_INTRINSIC_CLASS,其中UCLASS和UINTERFACE我们上文已经见识过了,而IMPLEMENT_INTRINSIC_CLASS是用以在代码中包装UModel,IMPLEMENT_CORE_INTRINSIC_CLASS是用来包装UField、UClass等引擎内建的接近,后两者中也都调用了IMPLEMENT_CLASS来促成力量。
流程图如下:
图片 1

想:为何要TClassCompiledInDefer和FCompiledInDefer两个静态初始化来报?
我们吧着眼到了这两者是逐一对应的,问题是怎么用简单只静态对象来分别采访,为何无一起二也平?关键在于我们先是要明白她二者的不同之处,前者的目的主要是为继续提供一个TClass::StaticClass的Register方法(其会触发GetPrivateStaticClassBody的调用,进而创造有UClass对象),而后者的目的是当其UClass身上延续调用构造函数,初始化属性和函数等局部注册操作。我们得大概明了啊即如是C++中new对象的星星点点只步骤,首先分配内存,继而在该内存上构造对象。我们以连续之挂号章节里还会继续讨论到是题材。

心想:为何要延期注册而不是直以static回调里实行?
无数人也许会见问,为什么static回调里都是先行将消息注册进array结构里,并没有呀其他操作,为何未直把承的操作直接当回调里调用了,这样结构反而略些。是如此没有错,但是还要我们吧设想到一个题目,UE4里大约1500几近只类似,如果都当static初始化阶段进行1500大多独像样的征集注册操作,那么main函数必须得相当好一阵子才会开始实践。表现及便用户双击了序,没反应,过了好巡,窗口才打开。因此static初始化回调里尽量少之召开作业,就是为尽快的增速程序启动之快慢。等窗口亮出了,array结构里多少现已闹矣,我们就可以施展手脚,多线程也好,延迟也好,都得以大大改善程序运行的心得。

Enum的收集

仍是齐文里的比代码,UENUM会生成:

static FCompiledInDeferEnum Z_CompiledInDeferEnum_UEnum_EMyEnum(EMyEnum_StaticEnum, TEXT("/Script/Hello"), TEXT("EMyEnum"), false, nullptr, nullptr);
//其定义:
struct FCompiledInDeferEnum
{
    FCompiledInDeferEnum(class UEnum *(*InRegister)(), const TCHAR* PackageName, const TCHAR* Name, bool bDynamic, const TCHAR* DynamicPackageName, const TCHAR* DynamicPathName)
    {
        if (bDynamic)
        {
            GetConvertedDynamicPackageNameToTypeName().Add(FName(DynamicPackageName), FName(Name));
        }
        UObjectCompiledInDeferEnum(InRegister, PackageName, DynamicPathName, bDynamic);
        //  static TArray<FPendingEnumRegistrant> DeferredCompiledInRegistration;

    }
};

于static阶段会朝着内存注册一个结构UEnum的函数指针用于回调:
图片 2
顾到此并不需要像UClassCompiledInDefer一样先生化作一个UClass
,因为UEnum并无是一个Class,并无Class那么多职能集聚,所以便比较简单一些。

Struct的收集

对此Struct,我们事先来看上篇里转变的代码:

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;    //static注册

相同是简单单static对象,前者FCompiledInDeferStruct继续向array结构里登记函数指针,后者有点异样,在一个结构名和对象的Map映射里登记“Struct相应的C++操作类”(后续解释)。

struct FCompiledInDeferStruct
{
    FCompiledInDeferStruct(class UScriptStruct *(*InRegister)(), const TCHAR* PackageName, const TCHAR* Name, bool bDynamic, const TCHAR* DynamicPackageName, const TCHAR* DynamicPathName)
    {
        if (bDynamic)
        {
            GetConvertedDynamicPackageNameToTypeName().Add(FName(DynamicPackageName), FName(Name));
        }
        UObjectCompiledInDeferStruct(InRegister, PackageName, DynamicPathName, bDynamic);// static TArray<FPendingStructRegistrant> DeferredCompiledInRegistration;
    }
};
void UScriptStruct::DeferCppStructOps(FName Target, ICppStructOps* InCppStructOps)
{
    TMap<FName,UScriptStruct::ICppStructOps*>& DeferredStructOps = GetDeferredCppStructOps();

    if (UScriptStruct::ICppStructOps* ExistingOps = DeferredStructOps.FindRef(Target))
    {
#if WITH_HOT_RELOAD
        if (!GIsHotReload) // in hot reload, we will just leak these...they may be in use.
#endif
        {
            check(ExistingOps != InCppStructOps); // if it was equal, then we would be re-adding a now stale pointer to the map
            delete ExistingOps;
        }
    }
    DeferredStructOps.Add(Target,InCppStructOps);
}

此外的,搜罗引擎里的代码,我们尚会见发现对UE4里内建的结构,比如说Vector,其IMPLEMENT_STRUCT(Vector)也会见相应的触发DeferCppStructOps的调用。
图片 3
这里的Struct也同Enum同理,因为并无是一个Class,所以并不需要比较麻烦的点滴步构造,凭着FPendingStructRegistrant就可以继续一步构造出UScriptStruct目标;对于内建的门类(如Vector),因其全无是“Script”的项目,所以便无欲UScriptStruct的构建,那么该安像BP暴露,我们累再详尽介绍。
再有少数瞩目的凡UStruct类型会配套一个ICppStructOps接口对象来治本C++struct对象的构造和析构工作,其用意就是在使对同曾经蹭除了项目的内存数据,我们怎么能于那达成对的组织结构对象数据还是析构。这个时段,如果我们会赢得一个合的ICppStructOps
指南针指为路安全之TCppStructOps<CPPSTRUCT>对象,就可知透过接口函数动态、多态、类型安全之实施组织与析构工作。

Function的收集

每当介绍了了Class、Enum、Struct之后,我们还忘记了有引擎内建的函数的音信收集。我们在前文中并不曾介绍至当下一点是盖UE已经提供了俺们一个BlueprintFunctionLibrary的类似来注册全局函数。而有些引擎内部定义出来的函数,也是烂分布于各处,也是要募起来的。
根本发生应声片近乎:

否得窥见出3单static对象收集至这些函数的音并报及相应的组织被去,流程图也:
图片 4
里头FNativeFunctionRegistrar用于向UClass里添加Native函数(区别为蓝图里定义之函数),另一个者,在UClass的RegisterNativeFunc相关函数里,也会将相应的Class内定义的函数添加至UClass内部的函数表里去。

UObject的收集

万一读者对象等团结分析源码,还见面有一个困惑,作为Object系统的根类,它是怎在无限开始之时光接触相应UClass的转变也?答案于最为初步之IMPLEMENT_VM_FUNCTION(EX_CallMath,
execCallMathFunction)调用上,其内部会随着触发UObject::StaticClass()的调用,作为最开头的调用,检测及UClass
从未有过变化,于是就会转化到GetPrivateStaticClassBody中错过特别成一个UClass*。
图片 5

总结

因篇幅有限,本文就上文,讨论了代码生成的信息是怎一步步收集至内存里的数据结构里去之,UE4利用了C++的static对象初始化模式,在程序最初启动之上,main之前,就收集及了装有的项目元数据、函数指针回调、名字、CRC等消息。到即,思路要生鲜明的,为各一个类代码生成自己的cpp文件(不需中心化的改动既来代码),进而以其转移的每个cpp文件里之所以static模式招致一普信息以便后续之采取。这也好不容易C++自己实现项目系统流行套路之一吧。
在生一个等级——注册,我们用讨论UE4接下去是哪些花应用这些信之。

引用

UE4.15.1


知乎专栏:InsideUE4
UE4深入学习QQ群:456247757(非新手入门群,请预念完官方文档和视频教程)
微信公众号:aboutue,关于UE的方方面面新闻资讯、技巧问答、文章发布,欢迎关注。
个人原创,未经授权,谢绝转载!

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图