UE 模块的加载与启动分析

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 启动的内容