深入UE——UObject(九)类型系统注册-InitUObject

引言

在上文中,我们讲解了CoreUObject模块的加载用UClassRegisterAllCompiledInClasses来生成各编译进来的类的UClass*对象,这些UClass*对象的数据还只是比较简单的版本,只调用过构造函数、设定了SuperStruct和ClassWithin。在这一步之后呢,我们继续按照流程,介绍跟UObject注册相关的FEngineLoop::AppInit

AppInit

程序的Init里做的事情比较简单,关键是最后一步用一个多播委托通知了程序初始化事件,让别的代码有机会继续进行一些初始化工作。

bool FEngineLoop::AppInit()
{
    //...做一些其他程序入口的初始化工作
    FCoreDelegates::OnInit.Broadcast(); //在前文注册的委托,在CoreUOject模块加载的时候指向了InitUObject
    return true;
}

InitUObject

继续跟踪代码:

void InitUObject()
{
    //...
    FCoreDelegates::OnExit.AddStatic(StaticExit);   //注册退出的事件
    //注册模块加载的
    FModuleManager::Get().OnProcessLoadedObjectsCallback().AddStatic(ProcessNewlyLoadedUObjects);
    //...
    StaticUObjectInit();//调用另一个函数
}

这里的重点是ProcessNewlyLoadedUObjects的注册,这个函数的构造生成类型系统一个非常重要的函数(下文讲解)。我们知道,UE的组织形式是Module,一个Module编译后可以生成一个dll。dll是可以动态加载的,因此如果在引擎初始化结束后,继续动态加载一个模块(即dll),根据C++机制,会触发dll里面的static变量初始化。因此元数据信息就又收集到了一些。我们就需要继续利用这些元数据信息来为这个新dll里定义的类构造类型的UClass*对象。总而言之,注册这个事件的目的是让新的模块在加载完毕后,让我们为dll里的native类构造类型对象。一个非常重要要知道的是,这里隐含的另一个意思是ProcessNewlyLoadedUObjects有可能会被调用多次。

StaticUObjectInit

void StaticUObjectInit()
{
    UObjectBaseInit();  //继续转发
    //最后,创建临时包
    GObjTransientPkg = NewObject<UPackage>(nullptr, TEXT("/Engine/Transient"), RF_Transient);
    GObjTransientPkg->AddToRoot();  //这个临时包总不会释放
    //...
}
//...
template< class T >
T* NewObject(UObject* Outer = (UObject*)GetTransientPackage())
{
    //...
}

继续转发调用,但我们发现在UObjectBaseInit初始化结束后,就已经可以开始NewObject了,标志着整个UObject系统的成功创建!GObjTransientPkg是个全局变量,所有没有Outer的对象都会放在这个包里。我们在NewObject的时候,如果不提供Outer,则会返回这个临时包,符合了UObject对象必须在UPackage里的一贯基本原则。

UObjectBaseInit

void UObjectBaseInit()
{
    //...
    GUObjectAllocator.AllocatePermanentObjectPool(SizeOfPermanentObjectPool);//初始化对象分配器
    GUObjectArray.AllocateObjectPool(MaxUObjects, MaxObjectsNotConsideredByGC, bPreAllocateUObjectArray);//初始化对象管理数组

    void InitAsyncThread();
    InitAsyncThread();  //初始化Package(uasset)的异步加载线程

    Internal::GObjInitialized = true;   //指定UObject系统初始化完毕

    UObjectProcessRegistrants();    //处理注册项
    //...
}

这个函数主要做了4件事: 1. 初始化UObject的内存分配存储系统和对象的Hash系统,这部分在下一个大章节讲解。 2. 创建了异步加载线程,用来后续Package(uasset)的加载。 3. GObjInitialized=true,这样在后续就可以用bool UObjectInitialized()来判断对象系统是否可用。 4. 继续转发到UObjectProcessRegistrants来把注册项一一处理。

UObjectProcessRegistrants

static void DequeuePendingAutoRegistrants(TArray<FPendingRegistrant>& OutPendingRegistrants)
{
    FPendingRegistrant* NextPendingRegistrant = GFirstPendingRegistrant;
    GFirstPendingRegistrant = NULL;
    GLastPendingRegistrant = NULL;
    while (NextPendingRegistrant)
    {
        FPendingRegistrant* PendingRegistrant = NextPendingRegistrant;
        OutPendingRegistrants.Add(*PendingRegistrant);
        NextPendingRegistrant = PendingRegistrant->NextAutoRegister;
        delete PendingRegistrant;
    };
}

static void UObjectProcessRegistrants()
{
    TArray<FPendingRegistrant> PendingRegistrants;
    DequeuePendingAutoRegistrants(PendingRegistrants);  //从链表中提取注册项列表

    for(int32 RegistrantIndex = 0;RegistrantIndex < PendingRegistrants.Num();++RegistrantIndex)
    {
        const FPendingRegistrant& PendingRegistrant = PendingRegistrants[RegistrantIndex];
        UObjectForceRegistration(PendingRegistrant.Object); //真正的注册
        DequeuePendingAutoRegistrants(PendingRegistrants);  //继续尝试提取
    }
}

可以看出这个函数的主要目的是从GFirstPendingRegistrant和GLastPendingRegistrant定义的链表抽取出来FPendingRegistrant的列表,然后一一用UObjectForceRegistration来注册。但是要注意在每一项注册之后,都要重复调用DequeuePendingAutoRegistrants一下来继续提取,这么做是因为在真正注册一个UObject的时候(后文谈到创建CDO和加载Package有可能引用到别的模块里的东西),里面有可能触发另一个Module的加载,从而导致有新的注册项进来。所以就需要不断的提取注册直到把所有处理完。

UObjectForceRegistration

void UObjectForceRegistration(UObjectBase* Object)
{
    TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap();//得到对象的注册信息
    FPendingRegistrantInfo* Info = PendingRegistrants.Find(Object);
    if (Info)   //有可能为空,因为之前已经被注册过了
    {
        const TCHAR* PackageName = Info->PackageName;//对象所在的Package
        const TCHAR* Name = Info->Name; //对象名字
        PendingRegistrants.Remove(Object);//删除
        Object->DeferredRegister(UClass::StaticClass(),PackageName,Name);//延迟注册
    }
}

需要注意的是,UObjectForceRegistration这个函数有可能在多个地方调用:

  1. 在UObjectProcessRegistrants里对一个个对象手动进行注册。
  2. UClass::CreateDefaultObject()内部用UObjectForceRegistration(ParentClass)来确认基类已经注册完成。 3. UE4CodeGen_Private::ConstructUClass()等构造类型对象的函数里用UObjectForceRegistration(NewClass)来保证该对象已经注册。

所以,在重复的调用的时候,需要先判断是否PendingRegistrants里还存在该元素。

UObjectBase::DeferredRegister

void UObjectBase::DeferredRegister(UClass *UClassStaticClass,const TCHAR* PackageName,const TCHAR* InName)
{
    // Set object properties.
    UPackage* Package = CreatePackage(nullptr, PackageName);    //创建属于的Package
    Package->SetPackageFlags(PKG_CompiledIn);
    OuterPrivate = Package; //设定Outer到该Package

    ClassPrivate = UClassStaticClass;   //设定属于的UClass*类型

    // Add to the global object table.
    AddObject(FName(InName), EInternalObjectFlags::None);   //注册该对象的名字
}
void UObjectBase::AddObject(FName InName, EInternalObjectFlags InSetInternalFlags)
{
    NamePrivate = InName;   //设定对象的名字
    //...
    //AllocateUObjectIndexForCurrentThread(this);
    //HashObject(this);
}

DeferredRegister其实才是对象真正注册的地方。很多朋友或许会疑惑,我们一直说注册注册,但具体什么是注册呢。DeferredRegister这个名字的包含的两个意思:

  1. Deferred是延迟的意思,区分于之前的UObjectBase::Register,延迟的意思是在对象系统初始化(GUObjectAllocator和GUObjectArray)之后的注册。Register的时候还不能正常NewObject和加载Package,而初始化之后这个阶段就可以开始正常的使用UObject系统的功能了。所以这里面才可以开始CreatePackage。
  2. Register注册,确定一点的意思是对代码里的class生成相应的UClass*对象并添加(注册)到全局对象数组里。

所以总结起来这里所做的是创建出UClass*的Outer指向的Package,并设置ClassPrivate(这里都是UClass*对象,所以其实都是UClass::StaticClass())。然后在AddObject里设置NamePrivate。因此这步之后这些一个个UClass*对象才有名字,之间的联系才算完整。 但同时也需要注意的是,这些UClass*对象里仍然没有UProperty和UFunciton,下一篇来讲解这些的构造生成。

总结

本篇主要是讲解了AppInit阶段里跟UObject有关的InitUObject操作,其中按顺序重要的操作有:

  1. ProcessNewlyLoadedUObjects回调的注册,让后续模块加载后可以调用该函数。
  2. 对象存储分配系统初始化:GUObjectAllocator和GUObjectArray初始化。
  3. UObjectProcessRegistrants里对每一项进行注册,创建Package,设置OuterPrivate,ClassPrivate,NamePrivate,并添加到全局对象数组里。
  4. 创建GObjTransientPkg临时包用来存放以后其他的对象。

到现在,我们就可以继续来总结下内存中的UClass*的互相关系。虽然各UClass*对象里面还有UProperty和UFuntion对象还没有创建,也还没有设置完成。但是对象之间互相的联系(OuterPrivate,ClassPrivate,SuperStruct,NamePrivate)这些值就已经设置完毕了。所以,朋友们,让我们闭上眼,在脑海里回顾一下,迄今为止构造的这些UClass*对象、各种UPackage对象、还有我们以后代码里自己创建的类和对象,它们的从属关系,和类型关系又是怎么样的呢?

SuperStruct

SuperStruct主要是用在UClass*对象之间表示类型的继承关系。UClass*对象和我们在代码里定义class是一一对应的通过SuperStruct组织形成了一棵类型树。 而对象的类型关系就是通过ClassPrivate来表达的了。

ClassPrivate

读者朋友们可以根据此图来验证一下自己的理解是否正确。

  • 特别需要主要的是名为“Class”的UClass*对象,其是通过UClass::StaticClass()生成创建的,它的ClassPrivate指向了自身!就像只衔尾蛇一样,这是个特殊情况,通过此可以判断一个对象是否是UClass本身。
  • UObject类本身类型关系也是用UClass来表示的。
  • 图上并没有列出各UClass*拥有的ClassDefaultObject,一是因为后续序列化再讲解,二是因为其不过也是个普通对象而已。
  • 各UPackage也不过是个UObject对象。所以虽然说我们创建的对象的Outer一般是UPackage,但是也可以是其他的普通Outer,从而用Outer的不断指向来组成一颗从属树。注意和Owner这个概念区分,Owner是Actor里定义的AActor* Owner变量,用在网络的领域里用来确定Relevant相关性的。

同时也让我们通过源码里的数据变量进一步归纳:

class COREUOBJECT_API UObjectBase
{
private:
    EObjectFlags    ObjectFlags;    //对象标志,定义了对象各种状态和特征
    int32           InternalIndex;  //对象的存储索引
    UClass*         ClassPrivate;
    FName           NamePrivate;
    UObject*        OuterPrivate;
};
class COREUOBJECT_API UStruct : public UField
{
private:
    UStruct* SuperStruct;
};

在内存中对象们通过这4个变量来定义了各种关系:

  1. NamePrivate:定义了对象的名字
  2. OuterPrivate:定义了对象的从属关系
  3. ClassPrivate:定义了对象的类型关系
  4. SuperStruct:定义了类型的继承关系

下篇,让我们继续把UClass*从骨架的状态填满,在ProcessNewlyLoadedUObjects里喂饱她们!

猜你喜欢

转载自blog.csdn.net/ttod/article/details/133254588