In-depth UE - UObject (8) type system registration - CoreUObject module loading

introduction

The previous article introduced the last step of the Static initialization phase to create the first UClass object. Then following the process of program startup, this article begins to introduce the process after the Main function entry comes in.

Please also note the following points:

  • The UE engine is so large that its initialization must go through a series of complicated processes starting from WinMain. This chapter only focuses on the content and processes related to the CoreUObject module, or the UObject system. Other initializations (such as window creation, thread startup , module loading, etc.) We will ignore it for now and dig a hole for subsequent explanation.
  • At the same time, in order to explain the process in the most concise way, we ignore the relevant function calling process content of the editor, and only care about the process under Runtime (that is, the process of running the game after packaging). The debugging process is to use the source code version of the engine, create a project, and first CookContentForWindows under Editor, then switch to Debug configuration in VS, compile and run. This way you can track and debug the contents of Game and Engine together.)
  • The code blocks connected by arrows in the flow chart do not mean that they are directly adjacent in the source code. There may still be other codes in the middle, but they are not related to the topic, so they are not shown. The arrow pointing to the right indicates nested calls of functions, and the further to the right, the deeper the nesting becomes; the arrow pointing downward indicates the sequential execution of code blocks within a function, and the arrow ending downward indicates that the function has completed execution.

Overall engine process

First, let’s take a rough look at the entire engine startup process when running the project. The green part indicates that the CoreUObject module is involved.

  • Static initialization refers to the collection process mentioned above.
  • Taking the Windows platform as an example, WinMain is the program entry defined in LaunchWindows.cpp.
int32 WINAPI WinMain( _In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char*, _In_ int32 nCmdShow )
{
    //...
    ErrorLevel = GuardedMain( CmdLine, hInInstance, hPrevInstance, nCmdShow );
    //...
    FEngineLoop::AppExit(); //程序的退出
    //...
    return ErrorLevel;
}
  • GuardedMain is where the actual program loop is implemented. The function at the beginning of Engine is actually just a simple transfer of the internal function of a global GEngineLoop.
FEngineLoop GEngineLoop;
int32 GuardedMain( const TCHAR* CmdLine, HINSTANCE hInInstance, HINSTANCE hPrevInstance, int32 nCmdShow )
{
   // make sure GEngineLoop::Exit() is always called.
    struct EngineLoopCleanupGuard 
    { 
        ~EngineLoopCleanupGuard()
        {
            EngineExit();   //保证在函数退出后能调用    转向 GEngineLoop.Exit();
        }
    } CleanupGuard;
    //...
    int32 ErrorLevel = EnginePreInit( CmdLine );    //预初始化  转向 GEngineLoop.PreInit( CmdLine );
    //...
#if WITH_EDITOR
    if (GIsEditor)
    {
        ErrorLevel = EditorInit(GEngineLoop);   //编辑器有其初始化版本
    }
    else
#endif
    {
        ErrorLevel = EngineInit();   //Runtime下的初始化    转向 GEngineLoop.Init();
    }
    //...
    while( !GIsRequestingExit )
    {
        EngineTick();   //无限循环的Tick    转向 GEngineLoop.Tick();
    }
    #if WITH_EDITOR
    if( GIsEditor )
    {
        EditorExit();   //编辑器的退出
    }
#endif
    return ErrorLevel;
}
  • FEngineLoop::PreInit is the first place we care about involving UObject startup.

FEngineLoop::PreInit

We know that UE is built on the UObject object system, so if other modules in the engine want to start loading, they must first initialize the CoreUObject module. Therefore, the pre-initialization part of the engine loop has to start loading CoreUObject.

int32 FEngineLoop::PreInit(const TCHAR* CmdLine)
{
    //...
    LoadCoreModules();  //加载CoreUObject模块
    //...
    //LoadPreInitModules();   //加载一些PreInit的模块,比如Engine,Renderer
    //...
    AppInit();  //程序初始化
    //...
    ProcessNewlyLoadedUObjects();   //处理最近加载的对象
    //...
    //LoadStartupModules();   //自己写的LoadingPhase为PreDefault的模块在这个时候加载
    //...
    GUObjectArray.CloseDisregardForGC();    //对象池启用,最开始是关闭的
    //...
    //NotifyRegistrationComplete();   //注册完成事件通知,完成Package加载
}

It can be seen from this pre-initialization process that CoreUObject is loaded first. The LoadCoreModules()internal call FModuleManager::Get().LoadModule(TEXT("CoreUObject"))will then trigger FCoreUObjectModule::StartupModule():

class FCoreUObjectModule : public FDefaultModuleImpl
{
    virtual void StartupModule() override
    {
        // Register all classes that have been loaded so far. This is required for CVars to work.
        UClassRegisterAllCompiledInClasses();   //注册所有编译进来的类,此刻大概有1728多个

        void InitUObject();
        FCoreDelegates::OnInit.AddStatic(InitUObject);  //先注册个回调,后续会在AppInit里被调用
        //...
    }
}

UClassRegisterAllCompiledInClasses

After expansion it is:

void UClassRegisterAllCompiledInClasses()
{
    TArray<FFieldCompiledInInfo*>& DeferredClassRegistration = GetDeferredClassRegistration();
    for (const FFieldCompiledInInfo* Class : DeferredClassRegistration)
    {
        //这里的Class其实是TClassCompiledInDefer<TClass>
        UClass* RegisteredClass = Class->Register();    //return TClass::StaticClass();
    }
    DeferredClassRegistration.Empty();  //前面返回的是引用,因此这里可以清空数据。
}
//...
static TArray<FFieldCompiledInInfo*>& GetDeferredClassRegistration()    //返回可变引用
{
    static TArray<FFieldCompiledInInfo*> DeferredClassRegistration; //单件模式
    return DeferredClassRegistration;
}

If you want to understand the logic here, you need to review and remind: (If you forget, please read the first three articles):

  1. GetDeferredClassRegistration()The elements inside were added during the static initialization mentioned in the previous collection article. They are added in the form of static TClassCompiledInDefer in XXX.gen.cpp.
  2. TClassCompiledInDefer<TClass>::Register()The interior is just a simple modulation TClass::StaticClass().
  3. TClass::StaticClass()DECLARE_CLASSIt is defined in the macro in XXX.generated.h , and it is simply transferred internally GetPrivateStaticClass(TPackage).
  4. GetPrivateStaticClass(TPackage)The function is implemented in IMPLEMENT_CLASSa macro. It will actually be called internally GetPrivateStaticClassBody. Internally, this function will create a UClass object and call Register(), which has been explained in detail in the previous article.
  5. To summarize, the logic here is to trigger the construction of UClass once for all classes defined in XXX.gen.cpp collected previously. In fact, only UObject is special and will trigger the construction when Static is initialized. Therefore, this process is actually the creation process of UClass for each class in the type system.
  6. This function will be called multiple times, and ProcessNewlyLoadedUObjectsthe call will still be triggered in subsequent calls. This FCoreUObjectModule::StartupModule()call is the first one, and the compiled classes loaded at this time are all linked in as soon as the engine starts.

Thinking: Guess which categories are generated first?

By adding Log printing to key codes (such as printing at the end of GetPrivateStaticClassBody), friends may find that various UClasses may be different in Editor mode and Runtime mode. On the one hand, this is because the order of dll link loading is different. On the other hand, it is also because the initialization order of static variables is uncertain, so the order of incoming FFieldCompiledInInfo will be different. But this actually doesn't have much impact, because there is a lot of protective code in UE's code to load the classes required for pre-processing. On the other hand, because the UClass generated at this stage only depends on SuperStruct and WithinClass, it does not matter if the order is uncertain. The "Object" Class initialized by Static is the first. In Editor mode, the CoreUObject module and other engine modules will be loaded first, and finally the Hello module (the reason is actually that the editor exe starts and then loads Hello.dll). The packaged game runtime is reversed, and the Hello module will be loaded first, and then the CoreUObject module (the reason is actually that other dlls are loaded internally after Hello.exe is started). Therefore, the order in which static variables are initialized is generally that the top-level dll will be initialized first.

Attached are the UClasses in CoreUObject to make them familiar, there are not many anyway:

//Static初始化:
Object
//CoreUObject:
GCObjectReferencer,TextBuffer,Field,Struct,ScriptStruct,Class,Package,Function,DelegateFunction,DynamicClass,PackageMap,Enum,EnumProperty,Property,Interface,LinkerPlaceholderClass,LinkerPlaceholderExportObject,LinkerPlaceholderFunction,MetaData,ObjectRedirector,ArrayProperty,ObjectPropertyBase,BoolProperty,ByteProperty,NumericProperty,ClassProperty,ObjectProperty,DelegateProperty,DoubleProperty,FloatProperty,IntProperty,Int16Property,Int64Property,Int8Property,InterfaceProperty,LazyObjectProperty,MapProperty,MulticastDelegateProperty,NameProperty,SetProperty,SoftClassProperty,SoftObjectProperty,StrProperty,StructProperty,UInt16Property,UInt32Property,UInt64Property,WeakObjectProperty,TextProperty

Thinking: Why are the registrations of Struct and Enum not reflected at this stage?

At this stage, we don't seem to see structures and enumerations defined in the module participating in the registration at this stage. In fact, it is because the metadata information generated after the structure is registered is saved in UScriptStruct, the enumeration corresponds to UEnum, and the class corresponds to UClass. Although we said in the previous article that the first UClass constructed is also a UObject, in fact, except for the UClass compiled in Native, the construction of other UObjects requires the assistance of its corresponding UClass, because UClass stores the class information. Constructor pointer. So if you want to construct UScriptStruct and UEnum objects, you must first have a UClass that describes the metadata information of these two classes. These two UClasses named "ScriptStruct" and "Enum" have been completed in the above-mentioned CoreUObject module loading. So there is nothing more to do. Therefore, at this stage, all basic types have actually been loaded, because the types are described by UClass.

Only UClass describes the object type. UScriptStruct and UEnum are two objects that save structure and enumeration metadata information. To construct an object, you need its UClass first.

At this point, I hope everyone can understand this sentence:

The type of UObject object is UClass, and UClass is a UObject object.

Summarize

Due to space limitations, this article has actually just talked about PreInit LoadCoreModules(). The purpose of this step is mainly to build the UClass of the classes defined in CoreUObject first. AppInit()But in fact, the values ​​inside these UClass objects have not been initialized and set, so the next step ProcessNewlyLoadedUObjects()will continue the registration process. The next article will explain the details in AppInit().

Guess you like

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