引擎源码分析:模块的加载和启动

Engine Source Analysis: Module Loading and Startup

UE是模块化的架构,Engine/Game Project/StandaloneApplication/Plugins都是Module(Unreal Engine API Reference列出了Engine提供的Module列表),本篇文章从FModuleManager的代码来分析一下UE的Module是如何通过FModuleManager::LoadModule加载和启动的。

Forward

本文的UE代码引用均为4.21版本。

注意:本文中说的IS_MONOLITHIC是模块使用者的*.target.cs中的LinkType,可以使用TargetLinkType.Monolithic来指定。

  • TargetLinkType.Monolithic(单片模式)的含义是将所有的代码放到一个单独的可执行文件中,编译时使用实现包含或者静态链接等方式,不依赖其他Module的DLL。

Module API Specifiers详见:Programming/ModulesModule API Specifiers

FModuleManager::LoadModule或者FModuleManager::LoadModuleWithFailureReason的调用会执行被加载Module的StartupModule.

Module Implementation

在UE中,项目的宏是IMPLEMENT_PRIMARY_GAME_MODULE,在创建项目后它一般是在ProjectName.cpp中定义:

1
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, VRExpansion, "VRExpansion" );

FDefaultGameModuleImpl是一个继承自FDefaultModuleImpl又间接继承自IModuleInterface的类,没有重写IModuleInterface的任何函数,仅作为一层封装的默认实现(游戏项目的启动也不需要通过Module的StartupModule驱动)。在Module中可以传入自己继承自IModuleInterface的类,重写其中的接口,就可以在Module加载时做特定的行为了(IModuleInterface的接口列在后面)。

IMPLEMENT_PRIMARY_GAME_MODULE宏首先替换到IMPLEMENT_GAME_MODULE然后又继续替换到的也是IMPLEMENT_MODULE.
在目前的引擎实现里看,这三个宏没有区别:

  • IMPLEMENT_PRIMARY_GAME_MODULE
  • IMPLEMENT_GAME_MODULE
  • IMPLEMENT_MODULE

一般插件中使用的都是IMPLEMENT_MODULE(该宏定义在Runtime\Core\Public\Modules\ModuleManger.h中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#if IS_MONOLITHIC

// If we're linking monolithically we assume all modules are linked in with the main binary.
#define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \
/** Global registrant object for this module when linked statically */ \
static FStaticallyLinkedModuleRegistrant< ModuleImplClass > ModuleRegistrant##ModuleName( #ModuleName ); \
/** Implement an empty function so that if this module is built as a statically linked lib, */ \
/** static initialization for this lib can be forced by referencing this symbol */ \
void EmptyLinkFunctionForStaticInitialization##ModuleName(){} \
PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)

#else

#define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \
\
/**/ \
/* InitializeModule function, called by module manager after this module's DLL has been loaded */ \
/**/ \
/* @return Returns an instance of this module */ \
/**/ \
extern "C" DLLEXPORT IModuleInterface* InitializeModule() \
{ \
return new ModuleImplClass(); \
} \
PER_MODULE_BOILERPLATE \
PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)

#endif //IS_MONOLITHIC

这个宏的含义是定义和导出一个通用的模块接口,供外部来驱动这个Module启动/停止或者其他,这个宏是必须的,有这个宏才能被UE的模块机制驱动。

IS_MONOLITHIC

在编译目标的target.csLinkTypeTargetLinkType.Monolithic的情况下,则编译时使用的时IS_MONOLITHIC逻辑。这种模式下Module的IMPLEMENT_MODULE宏则是构造了一个FStaticallyLinkedModuleRegistrant<ModuleImplClass>的static对象,在它的构造函数中会调用FModuleManager::RegisterStaticallyLinkedModule来将其添加到一个TMap对象(FModuleManager::StaticallyLinkedModuleInitializers)中,供后续LoadModule时查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
template< class ModuleClass >
class FStaticallyLinkedModuleRegistrant
{
public:

/**
* Explicit constructor that registers a statically linked module
*/
FStaticallyLinkedModuleRegistrant( const ANSICHAR* InModuleName )
{
// Create a delegate to our InitializeModule method
FModuleManager::FInitializeStaticallyLinkedModule InitializerDelegate = FModuleManager::FInitializeStaticallyLinkedModule::CreateRaw(
this, &FStaticallyLinkedModuleRegistrant<ModuleClass>::InitializeModule );

// Register this module
FModuleManager::Get().RegisterStaticallyLinkedModule(
FName( InModuleName ), // Module name
InitializerDelegate ); // Initializer delegate
}

/**
* Creates and initializes this statically linked module.
*
* The module manager calls this function through the delegate that was created
* in the @see FStaticallyLinkedModuleRegistrant constructor.
*
* @return A pointer to a new instance of the module.
*/
IModuleInterface* InitializeModule( )
{
return new ModuleClass();
}
};

class FModuleManager
{
// ...
public:
void RegisterStaticallyLinkedModule( const FName InModuleName, const FInitializeStaticallyLinkedModule& InInitializerDelegate )
{
StaticallyLinkedModuleInitializers.Add( InModuleName, InInitializerDelegate );
}

private:
/** Map of module names to a delegate that can initialize each respective statically linked module */
typedef TMap< FName, FInitializeStaticallyLinkedModule > FStaticallyLinkedModuleInitializerMap;
FStaticallyLinkedModuleInitializerMap StaticallyLinkedModuleInitializers;
// ...
};

IS_MONOLITHIC的情况下,函数FModuleManager::LoadModuleWithFailureReason(调用FModuleManager::LoadModule实现上也是转发到这里)通过对TMapModuleName-InitializerDelegate(FStaticallyLinkedModuleRegistrant::InitializeModule)的查找,可以得到一个指定模块的IModuleInterface接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// FModuleManager::LoadModuleWithFailureReason (Runtime\Core\Private\Modules\ModuleManager.cpp)
IModuleInterface* FModuleManager::LoadModuleWithFailureReason(const FName InModuleName, EModuleLoadResult& OutFailureReason)
{
// something....

// Make sure this isn't a module that we had previously loaded, and then unloaded at shutdown time.
//
// If this assert goes off, your trying to load a module during the shutdown phase that was already
// cleaned up. The easiest way to fix this is to change your code to query for an already-loaded
// module instead of trying to load it directly.
checkf((!ModuleInfo->bWasUnloadedAtShutdown), TEXT("Attempted to load module '%s' that was already unloaded at shutdown. FModuleManager::LoadModule() was called to load a module that was previously loaded, and was unloaded at shutdown time. If this assert goes off, your trying to load a module during the shutdown phase that was already cleaned up. The easiest way to fix this is to change your code to query for an already-loaded module instead of trying to load it directly."), *InModuleName.ToString());

// Check if we're statically linked with the module. Those modules register with the module manager using a static variable,
// so hopefully we already know about the name of the module and how to initialize it.
const FInitializeStaticallyLinkedModule* ModuleInitializerPtr = StaticallyLinkedModuleInitializers.Find(InModuleName);
if (ModuleInitializerPtr != nullptr)
{
const FInitializeStaticallyLinkedModule& ModuleInitializer(*ModuleInitializerPtr);

// Initialize the module!
ModuleInfo->Module = TUniquePtr<IModuleInterface>(ModuleInitializer.Execute());

if (ModuleInfo->Module.IsValid())
{
// Startup the module
ModuleInfo->Module->StartupModule();
// The module might try to load other dependent modules in StartupModule. In this case, we want those modules shut down AFTER this one because we may still depend on the module at shutdown.
ModuleInfo->LoadOrder = FModuleInfo::CurrentLoadOrder++;

// Module was started successfully! Fire callbacks.
ModulesChangedEvent.Broadcast(InModuleName, EModuleChangeReason::ModuleLoaded);

// Set the return parameter
LoadedModule = ModuleInfo->Module.Get();
}
else
{
UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because InitializeModule function failed (returned nullptr.)"), *InModuleName.ToString());
OutFailureReason = EModuleLoadResult::FailedToInitialize;
}
}
#if IS_MONOLITHIC
else
{
// Monolithic builds that do not have the initializer were *not found* during the build step, so return FileNotFound
// (FileNotFound is an acceptable error in some case - ie loading a content only project)
UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Module '%s' not found - its StaticallyLinkedModuleInitializers function is null."), *InModuleName.ToString());
OutFailureReason = EModuleLoadResult::FileNotFound;
}
#endif
// something....
};

!IS_MONOLITHIC

!IS_MONOLITHIC的情况下,它是导出了一个IModuleInterface* InitializeModule()的符号(在target.cs中没有指定为LinkType.Monolithic(IS_MONOLITHIC)的情况下每个Module是单独的链接库(lib/dll))。
在使用FModuleManager::LoadModuleWithFailureReason来加载指定Module时,首先通过FModuleManager::FindModulePaths来获取Module的链接库(DLL)路径:
它会依次查找:

  • FPlatformProcess::GetModulesDirectory
  • EngineBinariesDirectories
  • GameBinariesDirectories
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#if !IS_MONOLITHIC
void FModuleManager::FindModulePaths(const TCHAR* NamePattern, TMap<FName, FString> &OutModulePaths, bool bCanUseCache /*= true*/) const
{
if (!ModulePathsCache)
{
ModulePathsCache.Emplace();
const bool bCanUseCacheWhileGeneratingIt = false;
FindModulePaths(TEXT("*"), ModulePathsCache.GetValue(), bCanUseCacheWhileGeneratingIt);
}

if (bCanUseCache)
{
// Try to use cache first
if (const FString* ModulePathPtr = ModulePathsCache->Find(NamePattern))
{
OutModulePaths.Add(FName(NamePattern), *ModulePathPtr);
return;
}

// Wildcard for all items
if (FCString::Strcmp(NamePattern, TEXT("*")) == 0)
{
OutModulePaths = ModulePathsCache.GetValue();
return;
}

// Wildcard search
if (FCString::Strchr(NamePattern, TEXT('*')) || FCString::Strchr(NamePattern, TEXT('?')))
{
bool bFoundItems = false;
FString NamePatternString(NamePattern);
for (const TPair<FName, FString>& CacheIt : ModulePathsCache.GetValue())
{
if (CacheIt.Key.ToString().MatchesWildcard(NamePatternString))
{
OutModulePaths.Add(CacheIt.Key, *CacheIt.Value);
bFoundItems = true;
}
}

if (bFoundItems)
{
return;
}
}
}

// Search through the engine directory
FindModulePathsInDirectory(FPlatformProcess::GetModulesDirectory(), false, NamePattern, OutModulePaths);

// Search any engine directories
for (int Idx = 0; Idx < EngineBinariesDirectories.Num(); Idx++)
{
FindModulePathsInDirectory(EngineBinariesDirectories[Idx], false, NamePattern, OutModulePaths);
}

// Search any game directories
for (int Idx = 0; Idx < GameBinariesDirectories.Num(); Idx++)
{
FindModulePathsInDirectory(GameBinariesDirectories[Idx], true, NamePattern, OutModulePaths);
}
}

void FModuleManager::FindModulePathsInDirectory(const FString& InDirectoryName, bool bIsGameDirectory, const TCHAR* NamePattern, TMap<FName, FString> &OutModulePaths) const
{
// Figure out the BuildId if it's not already set.
if (!BuildId.IsSet())
{
FString FileName = FModuleManifest::GetFileName(FPlatformProcess::GetModulesDirectory(), false);

FModuleManifest Manifest;
if (!FModuleManifest::TryRead(FileName, Manifest))
{
UE_LOG(LogModuleManager, Fatal, TEXT("Unable to read module manifest from '%s'. Module manifests are generated at build time, and must be present to locate modules at runtime."), *FileName)
}

BuildId = Manifest.BuildId;
}

// Find all the directories to search through, including the base directory
TArray<FString> SearchDirectoryNames;
IFileManager::Get().FindFilesRecursive(SearchDirectoryNames, *InDirectoryName, TEXT("*"), false, true);
SearchDirectoryNames.Insert(InDirectoryName, 0);

// Enumerate the modules in each directory
for(const FString& SearchDirectoryName: SearchDirectoryNames)
{
FModuleManifest Manifest;
if (FModuleManifest::TryRead(FModuleManifest::GetFileName(SearchDirectoryName, bIsGameDirectory), Manifest) && Manifest.BuildId == BuildId.GetValue())
{
for (const TPair<FString, FString>& Pair : Manifest.ModuleNameToFileName)
{
if (Pair.Key.MatchesWildcard(NamePattern))
{
OutModulePaths.Add(FName(*Pair.Key), *FPaths::Combine(*SearchDirectoryName, *Pair.Value));
}
}
}
}
}
#endif

得到链接库的路径之后就通过FPlatformProcess::GetDllExport来获取DLL中的IModuleInterface::InitializeModule函数指针,然后调用IModuleInterface::StartupModule启动这个模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// FModuleManager::LoadModuleWithFailureReason (Runtime\Core\Private\Modules\ModuleManager.cpp)
IModuleInterface* FModuleManager::LoadModuleWithFailureReason(const FName InModuleName, EModuleLoadResult& OutFailureReason)
{
// something....
// Make sure that any UObjects that need to be registered were already processed before we go and
// load another module. We just do this so that we can easily tell whether UObjects are present
// in the module being loaded.
if (bCanProcessNewlyLoadedObjects)
{
ProcessLoadedObjectsCallback.Broadcast();
}

// Try to dynamically load the DLL

UE_LOG(LogModuleManager, Verbose, TEXT("ModuleManager: Load Module '%s' DLL '%s'"), *InModuleName.ToString(), *ModuleInfo->Filename);

if (ModuleInfo->Filename.IsEmpty() || !FPaths::FileExists(ModuleInfo->Filename))
{
TMap<FName, FString> ModulePathMap;
FindModulePaths(*InModuleName.ToString(), ModulePathMap);

if (ModulePathMap.Num() != 1)
{
UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' - %d instances of that module name found."), *InModuleName.ToString(), ModulePathMap.Num());
OutFailureReason = EModuleLoadResult::FileNotFound;
return nullptr;
}

ModuleInfo->Filename = MoveTemp(TMap<FName, FString>::TIterator(ModulePathMap).Value());
}

// Determine which file to load for this module.
const FString ModuleFileToLoad = FPaths::ConvertRelativePathToFull(ModuleInfo->Filename);

// Clear the handle and set it again below if the module is successfully loaded
ModuleInfo->Handle = nullptr;

// Skip this check if file manager has not yet been initialized
if (FPaths::FileExists(ModuleFileToLoad))
{
ModuleInfo->Handle = FPlatformProcess::GetDllHandle(*ModuleFileToLoad);
if (ModuleInfo->Handle != nullptr)
{
// First things first. If the loaded DLL has UObjects in it, then their generated code's
// static initialization will have run during the DLL loading phase, and we'll need to
// go in and make sure those new UObject classes are properly registered.
{
// Sometimes modules are loaded before even the UObject systems are ready. We need to assume
// these modules aren't using UObjects.
if (bCanProcessNewlyLoadedObjects)
{
// OK, we've verified that loading the module caused new UObject classes to be
// registered, so we'll treat this module as a module with UObjects in it.
ProcessLoadedObjectsCallback.Broadcast();
}
}

// Find our "InitializeModule" global function, which must exist for all module DLLs
FInitializeModuleFunctionPtr InitializeModuleFunctionPtr =
(FInitializeModuleFunctionPtr)FPlatformProcess::GetDllExport(ModuleInfo->Handle, TEXT("InitializeModule"));
if (InitializeModuleFunctionPtr != nullptr)
{
if ( ModuleInfo->Module.IsValid() )
{
// Assign the already loaded module into the return value, otherwise the return value gives the impression the module failed load!
LoadedModule = ModuleInfo->Module.Get();
}
else
{
// Initialize the module!
ModuleInfo->Module = TUniquePtr<IModuleInterface>(InitializeModuleFunctionPtr());

if ( ModuleInfo->Module.IsValid() )
{
// Startup the module
ModuleInfo->Module->StartupModule();
// The module might try to load other dependent modules in StartupModule. In this case, we want those modules shut down AFTER this one because we may still depend on the module at shutdown.
ModuleInfo->LoadOrder = FModuleInfo::CurrentLoadOrder++;

// Module was started successfully! Fire callbacks.
ModulesChangedEvent.Broadcast(InModuleName, EModuleChangeReason::ModuleLoaded);

// Set the return parameter
LoadedModule = ModuleInfo->Module.Get();
}
else
{
UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because InitializeModule function failed (returned nullptr.)"), *ModuleFileToLoad);

FPlatformProcess::FreeDllHandle(ModuleInfo->Handle);
ModuleInfo->Handle = nullptr;
OutFailureReason = EModuleLoadResult::FailedToInitialize;
}
}
}
else
{
UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because InitializeModule function was not found."), *ModuleFileToLoad);

FPlatformProcess::FreeDllHandle(ModuleInfo->Handle);
ModuleInfo->Handle = nullptr;
OutFailureReason = EModuleLoadResult::FailedToInitialize;
}
}
else
{
UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because the file couldn't be loaded by the OS."), *ModuleFileToLoad);
OutFailureReason = EModuleLoadResult::CouldNotBeLoadedByOS;
}
}
else
{
UE_LOG(LogModuleManager, Warning, TEXT("ModuleManager: Unable to load module '%s' because the file '%s' was not found."), *InModuleName.ToString(), *ModuleFileToLoad);
OutFailureReason = EModuleLoadResult::FileNotFound;
}
// something....
}

IModuleInterface

不论是通过IS_MONOLITHIC还是!IS_MONOLITHIC的方式,最终得到的都是Module的IModuleInterface(Runtime\Core\Public\Modules\ModuleInterface.h)接口,它里面声明了通用的Module接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class IModuleInterface
{
public:
// Note: Even though this is an interface class we need a virtual destructor here because modules are deleted via a pointer to this interface
virtual ~IModuleInterface(){}

// Called right after the module DLL has been loaded and the module object has been created
// Load dependent modules here, and they will be guaranteed to be available during ShutdownModule. ie:
// FModuleManager::Get().LoadModuleChecked(TEXT("HTTP"));
virtual void StartupModule(){}

// Called before the module has been unloaded
virtual void PreUnloadCallback(){}

// Called after the module has been reloaded

virtual void PostLoadCallback(){}

// Called before the module is unloaded, right before the module object is destroyed.
// During normal shutdown, this is called in reverse order that modules finish StartupModule().
// This means that, as long as a module references dependent modules in it's StartupModule(), it
// can safely reference those dependencies in ShutdownModule() as well.
virtual void ShutdownModule(){}

// Override this to set whether your module is allowed to be unloaded on the fly
// @return Whether the module supports shutdown separate from the rest of the engine.
virtual bool SupportsDynamicReloading(){return true;}

// Override this to set whether your module would like cleanup on application shutdown
// @return Whether the module supports shutdown on application exit
virtual bool SupportsAutomaticShutdown(){return true;}

// Returns true if this module hosts gameplay code
// @return True for "gameplay modules", or false for engine code modules, plugins, etc.
virtual bool IsGameModule() const{return false;}
};

Load Modules when the Engine Launch

前面提到,UE是模块化的架构,引擎的实现就是靠各个模块组合驱动的,所以在引擎启动的时候会启动各种相应的模块。
引擎的启动入口是在Launch模块中,由各个平台的main函数转发到GuardedMain(Engine\Source\Runtime\Launch\Private\Launch.cpp)中。
GuardedMain中依次执行GEngineLoop.PreInit(CmdLine)GEngineLoop.Init()

FEngingLoop::PreInit中,通过依次调用FEngineLoop::LoadCoreModules(LaunchEngineLoop.cpp#L1480)和FEngineLoop::LoadPreInitModules(LaunchEngineLoop.cpp#L1572)来加载引擎启动所必须的模块,模块的加载顺序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// LaunchEngineLoop.cpp
FEngineLoop::PreInit()
{
// ...
// L1480
// Load Core modules required for everything else to work (needs to be loaded before InitializeRenderingCVarsCaching)
if (!LoadCoreModules())
{
UE_LOG(LogInit, Error, TEXT("Failed to load Core modules."));
return 1;
}
// ...

// L1572
LoadPreInitModules();
// ...

// L2015
// note: Since 4.20 add ELoadingPhase::PreEarlyLoadingScreen Support.
// Load up all modules that need to hook into the loading screen
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreEarlyLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreEarlyLoadingScreen))
{
return 1;
}
// ...

// L2202
if ( !LoadStartupCoreModules() )
{
// At least one startup module failed to load, return 1 to indicate an error
return 1;
}
// ...

// L2211
// Load up all modules that need to hook into the loading screen
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreLoadingScreen))
{
return 1;
}
// ...

// L2310
if ( !LoadStartupModules() )
{
// At least one startup module failed to load, return 1 to indicate an error
return 1;
}
// ...

// L2457
// Load all the post-engine init modules
ensure(IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit));
ensure(IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit));
// ...

// L3044
// Load all the post-engine init modules
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
{
GIsRequestingExit = true;
return 1;
}
// ...

// L4279
// Load "pre-init" plugin modules
if (!ProjectManager.LoadModulesForProject(ELoadingPhase::PostConfigInit) || !PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::PostConfigInit))
{
return false;
}
// ...
}

EnginePreInit时加载Module的几个函数实现:

FEngineLoop::LoadCoreModules

1
2
3
4
5
6
7
8
9
10
// LaunchEngineLoop.cpp#L2679
bool FEngineLoop::LoadCoreModules()
{
// Always attempt to load CoreUObject. It requires additional pre-init which is called from its module's StartupModule method.
#if WITH_COREUOBJECT
return FModuleManager::Get().LoadModule(TEXT("CoreUObject")) != nullptr;
#else
return true;
#endif
}

FEngineLoop::LoadPreInitModules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// LaunchEngineLoop.cpp#L2690
void FEngineLoop::LoadPreInitModules()
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading PreInit Modules"), STAT_PreInitModules, STATGROUP_LoadTime);

// GGetMapNameDelegate is initialized here
#if WITH_ENGINE
FModuleManager::Get().LoadModule(TEXT("Engine"));

FModuleManager::Get().LoadModule(TEXT("Renderer"));

FModuleManager::Get().LoadModule(TEXT("AnimGraphRuntime"));

FPlatformApplicationMisc::LoadPreInitModules();

#if !UE_SERVER
if (!IsRunningDedicatedServer() )
{
if (!GUsingNullRHI)
{
// This needs to be loaded before InitializeShaderTypes is called
FModuleManager::Get().LoadModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer");
}
}
#endif

FModuleManager::Get().LoadModule(TEXT("Landscape"));

// Initialize ShaderCore before loading or compiling any shaders,
// But after Renderer and any other modules which implement shader types.
FModuleManager::Get().LoadModule(TEXT("ShaderCore"));

#if WITH_EDITORONLY_DATA
// Load the texture compressor module before any textures load. They may
// compress asynchronously and that can lead to a race condition.
FModuleManager::Get().LoadModule(TEXT("TextureCompressor"));
#endif

#endif // WITH_ENGINE

#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
// Load audio editor module before engine class CDOs are loaded
FModuleManager::Get().LoadModule(TEXT("AudioEditor"));
FModuleManager::Get().LoadModule(TEXT("AnimationModifiers"));
#endif
}

FPlatformApplicationMisc::LoadPreInitModules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// FPlatformApplicationMisc::LoadPreInitModules
void FWindowsPlatformApplicationMisc::LoadPreInitModules()
{
// D3D11 is not supported on WinXP, so in this case we use the OpenGL RHI
if(FWindowsPlatformMisc::VerifyWindowsVersion(6, 0))
{
//#todo-rco: Only try on Win10
const bool bForceD3D12 = FParse::Param(FCommandLine::Get(), TEXT("d3d12")) || FParse::Param(FCommandLine::Get(), TEXT("dx12"));
if (bForceD3D12)
{
FModuleManager::Get().LoadModule(TEXT("D3D12RHI"));
}
FModuleManager::Get().LoadModule(TEXT("D3D11RHI"));
}
FModuleManager::Get().LoadModule(TEXT("OpenGLDrv"));
}

FEngineLoop::LoadStartupCoreModules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// LaunchEngineLoop.cpp#L2739
bool FEngineLoop::LoadStartupCoreModules()
{
FScopedSlowTask SlowTask(100);

DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Startup Modules"), STAT_StartupModules, STATGROUP_LoadTime);

bool bSuccess = true;

// Load all Runtime modules
SlowTask.EnterProgressFrame(10);
{
FModuleManager::Get().LoadModule(TEXT("Core"));
FModuleManager::Get().LoadModule(TEXT("Networking"));
}

SlowTask.EnterProgressFrame(10);
FPlatformApplicationMisc::LoadStartupModules();

// initialize messaging
SlowTask.EnterProgressFrame(10);
if (FPlatformProcess::SupportsMultithreading())
{
FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging");
}

// Init Scene Reconstruction support
#if !UE_SERVER
if (!IsRunningDedicatedServer())
{
FModuleManager::LoadModuleChecked<IMRMeshModule>("MRMesh");
}
#endif

SlowTask.EnterProgressFrame(10);
#if WITH_EDITOR
FModuleManager::LoadModuleChecked<IEditorStyleModule>("EditorStyle");
#endif //WITH_EDITOR

// Load UI modules
SlowTask.EnterProgressFrame(10);
if ( !IsRunningDedicatedServer() )
{
FModuleManager::Get().LoadModule("Slate");

#if !UE_BUILD_SHIPPING
// Need to load up the SlateReflector module to initialize the WidgetSnapshotService
FModuleManager::Get().LoadModule("SlateReflector");
#endif // !UE_BUILD_SHIPPING
}

#if WITH_EDITOR
// In dedicated server builds with the editor, we need to load UMG/UMGEditor for compiling blueprints.
// UMG must be loaded for runtime and cooking.
FModuleManager::Get().LoadModule("UMG");
#else
if ( !IsRunningDedicatedServer() )
{
// UMG must be loaded for runtime and cooking.
FModuleManager::Get().LoadModule("UMG");
}
#endif //WITH_EDITOR

// Load all Development modules
SlowTask.EnterProgressFrame(20);
if (!IsRunningDedicatedServer())
{
#if WITH_UNREAL_DEVELOPER_TOOLS
FModuleManager::Get().LoadModule("MessageLog");
FModuleManager::Get().LoadModule("CollisionAnalyzer");
#endif //WITH_UNREAL_DEVELOPER_TOOLS
}

#if WITH_UNREAL_DEVELOPER_TOOLS
FModuleManager::Get().LoadModule("FunctionalTesting");
#endif //WITH_UNREAL_DEVELOPER_TOOLS

SlowTask.EnterProgressFrame(30);
#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
// HACK: load BT editor as early as possible for statically initialized assets (non cooked BT assets needs it)
// cooking needs this module too
FModuleManager::Get().LoadModule(TEXT("BehaviorTreeEditor"));

// Ability tasks are based on GameplayTasks, so we need to make sure that module is loaded as well
FModuleManager::Get().LoadModule(TEXT("GameplayTasksEditor"));

IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked<IAudioEditorModule>("AudioEditor");
AudioEditorModule->RegisterAssetActions();

// Load the StringTableEditor module to register its asset actions
FModuleManager::Get().LoadModule("StringTableEditor");

if( !IsRunningDedicatedServer() )
{
// VREditor needs to be loaded in non-server editor builds early, so engine content Blueprints can be loaded during DDC generation
FModuleManager::Get().LoadModule(TEXT("VREditor"));
}
// -----------------------------------------------------

// HACK: load EQS editor as early as possible for statically initialized assets (non cooked EQS assets needs it)
// cooking needs this module too
bool bEnvironmentQueryEditor = false;
GConfig->GetBool(TEXT("EnvironmentQueryEd"), TEXT("EnableEnvironmentQueryEd"), bEnvironmentQueryEditor, GEngineIni);
if (bEnvironmentQueryEditor
#if WITH_EDITOR
|| GetDefault<UEditorExperimentalSettings>()->bEQSEditor
#endif // WITH_EDITOR
)
{
FModuleManager::Get().LoadModule(TEXT("EnvironmentQueryEditor"));
}

// We need this for blueprint projects that have online functionality.
//FModuleManager::Get().LoadModule(TEXT("OnlineBlueprintSupport"));

if (IsRunningCommandlet())
{
FModuleManager::Get().LoadModule(TEXT("IntroTutorials"));
FModuleManager::Get().LoadModule(TEXT("Blutility"));
}

#endif //(WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))

#if WITH_ENGINE
// Load runtime client modules (which are also needed at cook-time)
if( !IsRunningDedicatedServer() )
{
FModuleManager::Get().LoadModule(TEXT("Overlay"));
}

FModuleManager::Get().LoadModule(TEXT("MediaAssets"));
#endif

FModuleManager::Get().LoadModule(TEXT("ClothingSystemRuntime"));
#if WITH_EDITOR
FModuleManager::Get().LoadModule(TEXT("ClothingSystemEditor"));
#endif

FModuleManager::Get().LoadModule(TEXT("PacketHandler"));

return bSuccess;
}

FPlatformApplicationMisc::LoadStartupModules

1
2
3
4
5
6
7
8
9
10
11
12
// FPlatformApplicationMisc::LoadStartupModules
void FWindowsPlatformApplicationMisc::LoadStartupModules()
{
#if !UE_SERVER
FModuleManager::Get().LoadModule(TEXT("XAudio2"));
FModuleManager::Get().LoadModule(TEXT("HeadMountedDisplay"));
#endif // !UE_SERVER

#if WITH_EDITOR
FModuleManager::Get().LoadModule(TEXT("SourceCodeAccess"));
#endif //WITH_EDITOR
}

FEngineLoop::LoadStartupModules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// LaunchEngineLoop.cpp#L2884
bool FEngineLoop::LoadStartupModules()
{
FScopedSlowTask SlowTask(3);

SlowTask.EnterProgressFrame(1);
// Load any modules that want to be loaded before default modules are loaded up.
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault))
{
return false;
}

SlowTask.EnterProgressFrame(1);
// Load modules that are configured to load in the default phase
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::Default) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::Default))
{
return false;
}

SlowTask.EnterProgressFrame(1);
// Load any modules that want to be loaded after default modules are loaded up.
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostDefault))
{
return false;
}

return true;
}

可以从上面的代码看到引擎PreInit启动的模块,以及启动的插件的模块(根据插件的LoadingPhase来决定启动时机)。

Load Plugin Modules

在上文中整理的代码中可以看到,项目的插件Module也是在FEngineLoop::PreInit中加载的。
写过UE的插件的都知道,UE的插件配置(.uplugin)里面有两个选项:TypeLoadingPhase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "TaskTools",
"Description": "",
"Category": "Other",
"CreatedBy": "",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
{
"Name": "TaskTools",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}

其中的Modules下的TypeLoadingPhase决定了引擎启动时该Plugin下的Module是否加载以及加载的时机。
Type是必要参数,LadingPhase是可选参数,默认是Default.

  • Type:决定了在不同运行环境的项目是否加载Module
  • LoadingPhase:决定了该Module的加载时机

Plugin:Type(Required)

Sets the type of Module. Valid options are RuntimeRuntimeNoCommandletDeveloperEditorEditorNoCommandlet, and Program. This type determines which types of applications this Plugin’s Module is suitable for loading in. For example, some plugins may include modules that are only designed to be loaded when the editor is running. Runtime modules will be loaded in all cases, even in shipped games. Developer modules will only be loaded in development runtime or editor builds, but never in shipping builds. Editor modules will only be loaded when the editor is starting up. Your Plugin can use a combination of modules of different types.

Type可选的项如下(在FModuleDescriptor::Read从json中读入):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace EHostType
{
enum Type
{
// Any target using the UE4 runtime
Runtime,
// Any target except for commandlet
RuntimeNoCommandlet,
// Any target or program
RuntimeAndProgram,
// Loaded only in cooked builds
CookedOnly,
// Loaded only when the engine has support for developer tools enabled
Developer,
// Loaded only by the editor
Editor,
// Loaded only by the editor, except when running commandlets
EditorNoCommandlet,
// Loaded only by programs
Program,
// Loaded only by servers
ServerOnly,
// Loaded only by clients
ClientOnly,
// NOTE: If you add a new value, make sure to update the ToString() method below!

Max
};
// ...
}

Plugin:LoadingPhase(Optional)

.uplugin Module LoadingPhase Descriptors.

If specified, controls when the plugin is loaded at start-up. This is an advanced option that should not normally be required. The valid options are Default(which is used when no LoadingPhase is specified), PreDefault, and PostConfigInitPostConfigInit enables the module to be loaded before the engine has finished starting up key subsystems. PreDefault loads just before the normal phase. Typically, this is only needed if you expect game modules to depend directly on content within your plugin, or types declared within the plugin’s code.

如果.upugin中没有指定LoadingPhase项,则默认是Default,LoadingPhase的可选项如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace ELoadingPhase
{
enum Type
{
/** Loaded before the engine is fully initialized, immediately after the config system has been initialized. Necessary only for very low-level hooks */
PostConfigInit,

/** Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers */
PreLoadingScreen,

/** Right before the default phase */
PreDefault,

/** Loaded at the default loading point during startup (during engine init, after game modules are loaded.) */
Default,

/** Right after the default phase */
PostDefault,

/** After the engine has been initialized */
PostEngineInit,

/** Do not automatically load this module */
None,

// NOTE: If you add a new value, make sure to update the ToString() method below!
Max
};
}

FProjectManager::LoadModulesForProject

FEngineLoop::PreInit中通过对IProjectManager::Get().LoadModulesForProject的调用来加载指定LoadingPhase的插件Module:

1
2
// Runtime\Project\Private\ModuleDescriptor.cpp
bool FProjectManager::LoadModulesForProject( const ELoadingPhase::Type LoadingPhase )

它里面将Module的加载转发到了FModuleDescriptor::LoadModulesForPhase,并对调用的结果做了错误检测(平常见到的插件的编译失败就是在这一步提示的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
bool FProjectManager::LoadModulesForProject( const ELoadingPhase::Type LoadingPhase )
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Game Modules"), STAT_GameModule, STATGROUP_LoadTime);

bool bSuccess = true;

if ( CurrentProject.IsValid() )
{
TMap<FName, EModuleLoadResult> ModuleLoadFailures;
FModuleDescriptor::LoadModulesForPhase(LoadingPhase, CurrentProject->Modules, ModuleLoadFailures);

if ( ModuleLoadFailures.Num() > 0 )
{
FText FailureMessage;
for ( auto FailureIt = ModuleLoadFailures.CreateConstIterator(); FailureIt; ++FailureIt )
{
const EModuleLoadResult FailureReason = FailureIt.Value();

if( FailureReason != EModuleLoadResult::Success )
{
const FText TextModuleName = FText::FromName(FailureIt.Key());

if ( FailureReason == EModuleLoadResult::FileNotFound )
{
FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleNotFound", "The game module '{0}' could not be found. Please ensure that this module exists and that it is compiled."), TextModuleName );
}
else if ( FailureReason == EModuleLoadResult::FileIncompatible )
{
FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleIncompatible", "The game module '{0}' does not appear to be up to date. This may happen after updating the engine. Please recompile this module and try again."), TextModuleName );
}
else if ( FailureReason == EModuleLoadResult::FailedToInitialize )
{
FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleFailedToInitialize", "The game module '{0}' could not be successfully initialized after it was loaded."), TextModuleName );
}
else if ( FailureReason == EModuleLoadResult::CouldNotBeLoadedByOS )
{
FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleCouldntBeLoaded", "The game module '{0}' could not be loaded. There may be an operating system error or the module may not be properly set up."), TextModuleName );
}
else
{
ensure(0); // If this goes off, the error handling code should be updated for the new enum values!
FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleGenericLoadFailure", "The game module '{0}' failed to load for an unspecified reason. Please report this error."), TextModuleName );
}

// Just report the first error
break;
}
}

FMessageDialog::Open(EAppMsgType::Ok, FailureMessage);
bSuccess = false;
}
}

return bSuccess;
}

FModuleDescriptor::LoadModulesForPhase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Runtime\Project\Private\ModuleDescriptor.cpp
// LoadingPhase - is want load Module LoadingPhase Descriptor
// Modules - is current project all modoules(CurrentProject->Modules)
// ModuleLoadErrors - is module-moduleLoadError mapping
void FModuleDescriptor::LoadModulesForPhase(ELoadingPhase::Type LoadingPhase, const TArray<FModuleDescriptor>& Modules, TMap<FName, EModuleLoadResult>& ModuleLoadErrors)
{
FScopedSlowTask SlowTask(Modules.Num());
for (int Idx = 0; Idx < Modules.Num(); Idx++)
{
SlowTask.EnterProgressFrame(1);
const FModuleDescriptor& Descriptor = Modules[Idx];

// Don't need to do anything if this module is already loaded
if (!FModuleManager::Get().IsModuleLoaded(Descriptor.Name))
{
if (LoadingPhase == Descriptor.LoadingPhase && Descriptor.IsLoadedInCurrentConfiguration())
{
// @todo plugin: DLL search problems. Plugins that statically depend on other modules within this plugin may not be found? Need to test this.

// NOTE: Loading this module may cause other modules to become loaded, both in the engine or game, or other modules
// that are part of this project or plugin. That's totally fine.
EModuleLoadResult FailureReason;
IModuleInterface* ModuleInterface = FModuleManager::Get().LoadModuleWithFailureReason(Descriptor.Name, FailureReason);
if (ModuleInterface == nullptr)
{
// The module failed to load. Note this in the ModuleLoadErrors list.
ModuleLoadErrors.Add(Descriptor.Name, FailureReason);
}
}
}
}
}

该函数的实现就是对当前项目的所有模块进行一次遍历,当遍历Module的LoadingPhase匹配传入的LoadingPhase参数以及该Module的Type符合当前的运行环境(通过FModuleDescriptor::IsLoadedInCurrentConfiguration来判断),则加载Module.

FModuleDescriptor::IsLoadedInCurrentConfiguration

FModuleDescriptor::IsLoadedInCurrentConfiguration的作用就是根据当前的模块的Type判断与当前的运行环境是否相匹配,从而返回bool决定是否加载Module。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
bool FModuleDescriptor::IsLoadedInCurrentConfiguration() const
{
// Check that the module is built for this configuration
if(!IsCompiledInCurrentConfiguration())
{
return false;
}

// Check that the runtime environment allows it to be loaded
switch (Type)
{
case EHostType::RuntimeAndProgram:
#if (WITH_ENGINE || WITH_PLUGIN_SUPPORT)
return true;
#endif
break;

case EHostType::Runtime:
#if (WITH_ENGINE || WITH_PLUGIN_SUPPORT) && !IS_PROGRAM
return true;
#endif
break;

case EHostType::RuntimeNoCommandlet:
#if (WITH_ENGINE || WITH_PLUGIN_SUPPORT) && !IS_PROGRAM
if(!IsRunningCommandlet()) return true;
#endif
break;

case EHostType::CookedOnly:
return FPlatformProperties::RequiresCookedData();

case EHostType::Developer:
#if WITH_UNREAL_DEVELOPER_TOOLS
return true;
#endif
break;

case EHostType::Editor:
#if WITH_EDITOR
if(GIsEditor) return true;
#endif
break;

case EHostType::EditorNoCommandlet:
#if WITH_EDITOR
if(GIsEditor && !IsRunningCommandlet()) return true;
#endif
break;

case EHostType::Program:
#if WITH_PLUGIN_SUPPORT && IS_PROGRAM
return true;
#endif
break;

case EHostType::ServerOnly:
return !FPlatformProperties::IsClientOnly();

case EHostType::ClientOnly:
return !IsRunningDedicatedServer();

}

return false;
}

至此,引擎启动时加载的引擎Module和项目的插件Module都启动完毕了。

ChangeLog

  • 2019-03-20 16:24:32 增加引擎启动时加载模块的内容
  • 2019-03-21 12:41:52 增加插件Module启动的内容
全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:引擎源码分析:模块的加载和启动
文章作者:查利鹏
发布时间:2019年03月19日 11时11分
更新时间:2019年03月30日 15时38分
本文字数:本文一共有5.5k字
原始链接:https://imzlp.com/posts/24007/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!