UE Build System:Target and Module

Module是构成Unreal的基本元素,每一个Module封装和实现了一组功能,并且可以供其他的Module使用,整个Unreal Engine就是靠各个Module组合驱动的,连我们创建的游戏项目本身,都是一个单独的Module。

那么UE又是怎么创建和构建这这些Module的呢?这是写这篇文章的主要目的,研究一下Unreal的构建系统以及它们(Target和Module)支持的各种属性。

建议在看这篇文章之前先看一下我之前的这篇文章:Build flow of the Unreal Engine4 project,主要内容是大致过一遍UE的构建流程,本篇文章只是UE构建系统中的一环。

对于UE项目比较熟悉的都知道,当使用UE创建一个C++游戏项目时,会在项目路径下创建Source文件夹,默认包含了下列文件:

1
2
3
4
5
6
7
8
9
10
Example\GWorld\Source>tree /a /f
| GWorld.Target.cs
| GWorldEditor.Target.cs
|
\---GWorld
GWorld.Build.cs
GWorld.cpp
GWorld.h
GWorldGameModeBase.cpp
GWorldGameModeBase.h

其中,*.Target.cs*.Build.cs是Unreal构建系统的实际控制者,UBT通过扫描这两个文件来确定整个编译环境,它们也是本篇文章研究的重点。
它们的职责各不相同:

  • *.Target.cs控制的是生成的可执行程序的外部编译环境,就是所谓的Target。比如,生成的是什么Type(Game/Client/Server/Editor/Program),开不开启RTTI(bForceEnableRTTI),CRT使用什么方式链接(bUseStaticCRT) 等等。
  • *.Build.cs控制的是Module编译过程,由它来控制所属Module的对其他Module的依赖、文件包含、链接、宏定义等等相关的操作,*.Build.cs告诉UE的构建系统,它是一个Module,并且编译的时候要做哪些事情。

以一言以蔽之:与外部编译环境相关的都归*.target.cs管,与Module自身相关的都归*.build.cs管。

插个题外话,在GWorld.hGWorld.cpp中定义的是Module真正的执行逻辑,使用IMPLEMENT_MODULE定义。UE中所有的Module都是继承自IModuleInterface,具有以下接口:

1
2
3
4
5
6
7
8
9
10
11
12
class IModuleInterface
{
public:
virtual ~IModuleInterface();
virtual void StartupModule();
virtual void PreUnloadCallback();
virtual void PostLoadCallback();
virtual void ShutdownModule();
virtual bool SupportsDynamicReloading();
virtual bool SupportsAutomaticShutdown();
virtual bool IsGameModule(); const
};

通过IModuleInterface来驱动Module的启动与关闭,不过一般Game Module不使用这个控制游戏流程。
这部分的详细内容可以看我之前的文章:UE4 Modules:Load and Startup

Target

每一个基于Unreal的项目,都有一个Tergat.cs,具有一个继承自TargetRules的类定义;并且默认需要关联着一个同名(非必要,但建议)的Module的定义,否则编译时会有Module未定义错误,它的含意时将指定的Module编译到Target中:

1
UnrealBuildTool : error : Could not find definition for module 'GWorld' (referenced via GWorld.Target.cs)

Target关联的Module的名字可以通过ExtraModuleNames来指定:

1
2
3
4
5
6
7
8
public class GWorldTarget : TargetRules
{
public GWorldTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
ExtraModuleNames.AddRange( new string[] { "GWorld" } );
}
}

上面指定的是GWorld,UBT解析的时候就会去找GWorld这个Module的定义,也就是GWorld.build.cs这个文件中的GWorld类定义,如果没有就会产生上面的Module未定义错误。

注意,与Target关联的Module不仅仅只是一个指定的名字这么简单,所有代码中使用的XXXX_API都是与Module的名字相关的。

如果我进行以下改动:ExtraModuleNames.AddRange( new string[] { "GWorldAAA" } );,那么需要对项目中所有的源文件进行的改动有:

  1. 将原有的GWorld.build.cs文件改名为GWorldAAA.build.cs,并将文件内容的所有GWorld替换为GWorldAAA
  2. 将项目内所有头文件的GWORLD_API改名为GWORLDAAA_API,因为XXX_API的导出符号是依赖于ModuleName的;

实在是个不小的工作量,所以还是建议将ExtraModuleNames中指定的名字与Game Module同名。
通过上面的内容,我们可以知道了Target.cs是如何与Build.cs关联的。那么,其实Game/Server/Client/EditorTarget可以共用同一个Module,将他们的ExtraModuleNames都设置成同一个就可以了(如果你想要针对每个Target类型单独写也可以)。

TargetRules的代码在UnrealBuildTools/Configuration/ModuleRulesReadOnlyTargetRules也定义其中),可以看一下所支持参数的默认值;UE对Target支持属性的描述文档:Targets

但是UE的官方文档里面也只是代码里的注释,有些描述看了之后摸不着头脑,后面我会分析一下TargetRule一些属性的含义,先埋个坑。

Type(TargetType)

TargetRules中的属性Type,其类型为TargetType,定义为TargetRules.cs中,是指定项目要编译出来的是什么程序。

  • Game - A standalone game which requires cooked data to run.
  • Client - Same as Game, but does not include any server code. Useful for networked games.
  • Server - Same as Game, but does not include any client code. Useful for dedicated servers in networked games.
  • Editor - A target which extends the Unreal Editor.
  • Program - A standalone utility program built on top of the Unreal Engine.

LinkType(TargetLinkType)

TargetRules中的LinkType,其类型为TargetLinkType,定义在TargetRules.cs中,是指定项目的链接类型。

TargetLinkType具有三个枚举值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// Specifies how to link all the modules in this target
/// </summary>
[Serializable]
public enum TargetLinkType
{
/// <summary>
/// Use the default link type based on the current target type
/// </summary>
Default,

/// <summary>
/// Link all modules into a single binary
/// </summary>
Monolithic,

/// <summary>
/// Link modules into individual dynamic libraries
/// </summary>
Modular,
}
  • TargetLinkType.DefaultLinkType的默认值,在此种状态下,如果当前TargetTypeEditor则使用Modular类型,链接所有的模块的方式为动态链接库。
  • TargetLinkType.Modular:以动态链接库的方式链接Module
  • TargetLinkType.Monolithic:将所有的模块链接到单个文件(静态链接)

可以通过修改LinkType来修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// Backing storage for the LinkType property.
/// </summary>
[RequiresUniqueBuildEnvironment]
[CommandLine("-Monolithic", Value ="Monolithic")]
[CommandLine("-Modular", Value ="Modular")]
TargetLinkType LinkTypePrivate = TargetLinkType.Default;

/// <summary>
/// Specifies how to link modules in this target (monolithic or modular). This is currently protected for backwards compatibility. Call the GetLinkType() accessor
/// until support for the deprecated ShouldCompileMonolithic() override has been removed.
/// </summary>
public TargetLinkType LinkType
{
get
{
return (LinkTypePrivate != TargetLinkType.Default) ? LinkTypePrivate : ((Type == global::UnrealBuildTool.TargetType.Editor) ? TargetLinkType.Modular : TargetLinkType.Monolithic);
}
set
{
LinkTypePrivate = value;
}
}

Name(string)

Target的名字,只读属性,传进来的项目名字。

Platform(UnrealTargetPlatform)

Platform的类型为UnrealTargetPlatform,它是一个枚举,定义在UnrealBuildTool\Configuration\UEBuildTarget.cs

它记录着当前Target的平台信息,如Win32/Win64等等,目前UE_4.22的版本支持的平台为:

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
public enum UnrealTargetPlatform
{
/// <summary>
/// Unknown target platform
/// </summary>
Unknown,

/// <summary>
/// 32-bit Windows
/// </summary>
Win32,

/// <summary>
/// 64-bit Windows
/// </summary>
Win64,

/// <summary>
/// Mac
/// </summary>
Mac,

/// <summary>
/// XboxOne
/// </summary>
XboxOne,

/// <summary>
/// Playstation 4
/// </summary>
PS4,

/// <summary>
/// iOS
/// </summary>
IOS,

/// <summary>
/// Android
/// </summary>
Android,

/// <summary>
/// HTML5
/// </summary>
HTML5,

/// <summary>
/// Linux
/// </summary>
Linux,

/// <summary>
/// All desktop platforms
/// </summary>
AllDesktop,

/// <summary>
/// TVOS
/// </summary>
TVOS,

/// <summary>
/// Nintendo Switch
/// </summary>
Switch,

/// <summary>
/// NDA'd platform Quail
/// </summary>
Quail,

/// <summary>
/// Confidential platform
/// </summary>
Lumin,
}

我们可以在build.cs或者target.cs中通过判断Platform来做不同的事情。

如:

1
2
3
4
if(Target.Platform != UnrealTargetPlatform.Win32 && Target.Platform != UnrealTargetPlatform.Win64)
{
PublicDefinitions.Add("HAVE_PTHREAD");
}

IsInPlatformGroup

这是一个函数bool IsInPlatformGroup(UnrealPlatformGroup Group),定义在TargetRules.cs中,它用来判断当前的Platform是否输入某一组。

需要传入的参数为UnrealTargetformGroup枚举类型,它定义在UEBuildTarget.cs中:

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
/// <summary>
/// Platform groups
/// </summary>
public enum UnrealPlatformGroup
{
/// <summary>
/// this group is just to lump Win32 and Win64 into Windows directories, removing the special Windows logic in MakeListOfUnsupportedPlatforms
/// </summary>
Windows,

/// <summary>
/// Microsoft platforms
/// </summary>
Microsoft,

/// <summary>
/// Apple platforms
/// </summary>
Apple,

/// <summary>
/// making IOS a group allows TVOS to compile IOS code
/// </summary>
IOS,

/// <summary>
/// Unix platforms
/// </summary>
Unix,

/// <summary>
/// Android platforms
/// </summary>
Android,

/// <summary>
/// Sony platforms
/// </summary>
Sony,

/// <summary>
/// Target all desktop platforms (Win64, Mac, Linux) simultaneously
/// </summary>
AllDesktop,
}

Configuration(UnrealTargetConfiguration)

当前编译的配置,类型为UnrealTargetConfiguration的枚举,定义在UEBuildTarget.cs中,由VS中的Configuration构造而来,如:

  • Development
  • Shipping
  • DebugGame
  • Debug
  • Test
  • Unknow

也就是通过这个设置,UBT才在编译环境中添加了下列宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public override void SetUpConfigurationEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment)
{
// other code
UnrealTargetConfiguration CheckConfig = Target.Configuration;
switch (CheckConfig)
{
default:
case UnrealTargetConfiguration.Debug:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_DEBUG=1");
break;
case UnrealTargetConfiguration.DebugGame:
// Default to Development; can be overridden by individual modules.
case UnrealTargetConfiguration.Development:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_DEVELOPMENT=1");
break;
case UnrealTargetConfiguration.Shipping:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_SHIPPING=1");
break;
case UnrealTargetConfiguration.Test:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_TEST=1");
break;bUseDebugCRT
}
// other code
}

Architecture(string)

所运行的平台的架构信息:x86/arm等等。

CppStandard(CppStandardVersion)

用于指定编译项目时所用的C++标准版本(在新版本引擎(4.23)中才有)。
CppStandardVersion

  • Latast
  • Cpp17
  • Cpp14

这个选项本质上就是将/std:c++xxx添加到VS的编译选项中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void AppendCLArguments_CPP(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// other code...
if(CompileEnvironment.CppStandard >= CppStandardVersion.Latest)
{
Arguments.Add("/std:c++latest");
}
else if(CompileEnvironment.CppStandard >= CppStandardVersion.Cpp17)
{
Arguments.Add("/std:c++17");
}
else if(CompileEnvironment.CppStandard >= CppStandardVersion.Cpp14)
{
Arguments.Add("/std:c++14");
}
// other code...
}

bUseDebugCRT(bool)

用来控制输出的Runtime Librart类型是MT还是MD
还用来控制添加_DEBUGNODEBUG宏:

1
2
3
4
5
6
7
8
9
10
11
12
public override void SetUpConfigurationEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment)
{
if (GlobalCompileEnvironment.bUseDebugCRT)
{
GlobalCompileEnvironment.Definitions.Add("_DEBUG=1"); // the engine doesn't use this, but lots of 3rd party stuff does
}
else
{
GlobalCompileEnvironment.Definitions.Add("NDEBUG=1"); // the engine doesn't use this, but lots of 3rd party stuff does
}
// other code
}

ProjectDefinitions(List<string>)

为当前项目添加的宏定义,在整个项目中可用。

GlobalDefinitions(List<string>)

添加在整个Target中都可以用的宏定义。

bShouldCompileAsDLL(bool)

将Target编译为DLL,为true时要求LinkTypeMonolithic

1
2
3
4
5
/// <summary>
/// Whether this target should be compiled as a DLL. Requires LinkType to be set to TargetLinkType.Monolithic.
/// </summary>
[RequiresUniqueBuildEnvironment]
public bool bShouldCompileAsDLL = false;

AdditionalCompilerArguments(String)

传递给编译器的参数。

AdditionalLinkerArguments(String)

传递给连接器的参数。

bUsesSlate(bool)

控制打包时时候把Slate相关的图片资源打包到pak中。

bUseInlining(bool)

是否开启内联优化,内联的本质是把展开函数,降低函数调用的开销。

但可能会存在一些问题,如引擎中的Engine模块中的FStreamingLevelsToConsider函数,它并没有导出符号ENGINE_API

而在Engine/World.h中的IsStreamingLevelBeingConsidered函数,以头文件中定义的形式调用了它:

Engine/World.h
1
2
/** Returns true if StreamingLevel is part of the levels being considered for update */
bool IsStreamingLevelBeingConsidered(ULevelStreaming* StreamingLevel) const { return StreamingLevelsToConsider.Contains(StreamingLevel); }

在头文件中定义的函数,是由编译器实现的隐式内联的,如果对World->IsStreamingLevelBeingConsidered的调用,在其他的模块中被内联,会导致链接错误:

1
2
3
4
5
Undefined symbols for architecture x86_64:
"FStreamingLevelsToConsider::Contains(ULevelStreaming*) const", referenced from:
UWorldMgr::WorldCompositionTick(float) in Module.FGame.8_of_8.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

因为内联展开之后,就相当于在调用处直接执行,而恰好FStreamingLevelsToConsider符号未导出,所以会产生链接错误。

要解决这样的问题,要么修改引擎,导出符号,要么在target.cs中关闭内联:

target.cs
1
2
bOverrideBuildEnvironment = true;
bUseInlining = false;

在UBT的代码里有检测它的值,然后添加实际的编译参数:

1
2
3
4
5
// 
if (!CompileEnvironment.bUseInlining)
{
Result += " -fno-inline-functions";
}

Module

Target类似,每一个Unreal的Module,都有一个专属的ModuleName.Build.cs里面定义着专属的ModuleName类,它由ModuleRules继承而来,我们对Module构建时进行的操作就是通过它来控制。

注意:不管是Game Module还是Plugin Module,只要是项目依赖的Module,编译时它们都会接收到当前使用的Target信息。

ModuleRules的代码在UnrealBuildTools/Configuration/ModuleRules,同样可以看一下支持的属性默认值;UE对Modules描述的官方文档:Modules,这里也同样只有代码的注释内容,没有实际例子,我就先来分析一些在工程中常见的Build.cs中属性的含义。

*.Build.cs中可以通过它构造接收的ReadOnlyTargetRules Target参数来获取*.Target.cs中的属性信息。

1
2
3
4
5
6
7
8
9
10
11
using UnrealBuildTool;
using System.IO;

public class GWorld : ModuleRules
{
public GWorld(ReadOnlyTargetRules ReadOnlyTargetRules) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
// something
}
}

通过Target对象,可以在*.build.cs中控制对不同的平台(Platform),架构(Architecture),以及其他的选项来对Module进行不同的操作(比如定义不同的宏/包含不同的ThridParty/链接不同的Lib等等)。

ModuleDirectory

  • string ModuleDirectory:项目的源码路径PROJECT_NAME/Source/PROJECT_NAME的绝对路径。

EngineDirectory

  • string EngineDirectory:引擎目录Engine/在当前环境的绝对路径。

PublicAdditionalLibraries

添加静态链接库文件(注意与PublicLibraryPaths的区别),一般是用于第三方库的链接。

1
2
3
4
5
6
7
8
PublicAdditionalLibraries.AddRange(
new string[]
{
Path.Combine(ThridPartyPath,"protobuf/lib/Win64/MD/Release","libprotobuf.lib"),
Path.Combine(ThridPartyPath,"protobuf/lib/Win64/MD/Release","libprotobuf-lite.lib"),
Path.Combine(ThridPartyPath,"protobuf/lib/Win64/MD/Release","libprotoc.lib"),
}
);

详细的内容可以看:Linking Static Libraries Using The Build System

同样可以用在DLL的导入库,与PublicDelayLoadDLLsRuntimeDependencies配合使用。

PublicAdditionalShadowFiles

当执行远程编译的时候,指定当前模块需要复制到远程服务器上的文件,确保能够链接成功。

如远程打包IOS平台时,需要把当前模块依赖的静态链接库添加到里面(如Game模块依赖某个插件中的External模块)。

RuntimeDependencies

  • list<RuntimeDependency> RuntimeDependencies:Module在运行时依赖的文件(.so/.dll等),打包时将会拷贝到存储目录。

在打包Windows时会直接把文件拷贝到打包的对应目录下,但是在Android上会把文件放到Apk包的main.obb.webp中。

PublicDelayLoadDLLs

  • List<string> PublicDelayLoadDLLs:延迟加载的DLL列表,通常用于第三方库。
1
2
3
// build.cs
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "myExternalLib.lib"));
PublicDelayLoadDLLs.Add("myExternalLib.dll");

含义是不在程序启动时立即加载DLL的列表,等到首次需要使用他们的符号后再进行加载。这样可以在模块的StartupModule中自行指定位置并加载他们,从而实现可以不把dll放到exe的目录。

1
2
3
4
5
6
FString AbsPath = FileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*MyLibPath);
FPlatformProcess::AddDllDirectory(AbsPath)
FPlatformProcess::PushDllDirectory(*AbsPath);
// direct call dll function
// or load dll handle
// void* DLLHandle3 = FPlatformProcess::GetDllHandle( L"myExternalLib.dll" );

PS:配合PublicAdditionalLibraries可以用在使用DLL导入库的第三方库。

  1. 使用PublicAdditionalLibraries添加lib
  2. DLL的名字添加至PublicDelayLoadDLLs
  3. 使用RuntimeDependencies打包时拷贝dll
  4. 如果拷贝到的目录不是exe路径,需要StartupModule里执行AddDllDirectoryPushDllDirectory把dll的路径添加到里面

    PublicDelayLoadDLLs只添加xxxx.dll 就可以了,不需要路径。

我使用GoogleInstantPreview测试使用DLL+导入库并将DLL放在非exe目录的例子:ue4-plugin-GoogleInstanceIns.7z

PublicDefinitions

  • List<string> PublicDefinitions:为当前Module添加公开宏定义,等同于传统VS项目在项目设置中添加一个预处理宏。

它被UBT分析之后会在产生一个Definitions.PROJECT_NAME.h的头文件,里面定了各种宏。

1
Intermediate\Build\Win64\UE4Editor\Development\ReflectionExample\Definitions.ReflectionExample.h

PublicSystemIncludePaths

  • List<string> PublicSystemIncludePaths:文档介绍是用于添加系统的Include路径,与PublicIncludePaths的区别是会跳过头文件解析检查(但是经我测试,使用这种方式包含的代码依然会检测下列错误(UE_4.20)):
1
error : Expected mpack-platform.h to be first header included.

注意:如果不指定路径,则默认的IncludePath路径是Engine/Source

比如:

1
2
3
4
5
PublicSystemIncludePaths.AddRange(
new string[] {
"TEST_LIB"
}
);

它表示的路径是:

1
D:\UnrealEngine\Epic\UE_4.21\Engine\Source\TEST_LIB

所有可以在*.build.cs中指定的*IncludePaths,默认的路径都是Engine/Source.

PrivateRuntimeLibraryPaths

  • List<string> PrivateRuntimeLibraryPaths:运行时库的搜索路径。例如.so或者.dll

PublicRuntimeLibraryPaths

  • List<string> PublicRuntimeLibraryPaths:运行时库的搜索路径。例如.so或者.dll

因为动态链接库的查找路径默认只有:

  1. 系统的PATH路径;
  2. 可执行程序的当前目录;

如果我们的动态链接库在其他的位置,运行时就会错误,可以通过PublicRuntimeLibraryPaths或者PrivateRuntimeLibraryPaths来添加。

PublicLibraryPaths

添加链接库文件的路径,如在源码中使用的:

1
#pragma comment(lib,"Lua.lib")

可以通过PublicLibraryPaths来添加依赖的Lib。

DynamicallyLoadedModuleNames

  • List<string> DynamicallyLoadedModuleNames:添加需要运行时动态加载的Module,使用FModuleManager::LoadModuleChecked<MODULE_TYPE>(TEXT("MODULE_NAME"))等函数启动。
1
2
// e.g
FModuleManager::LoadModuleChecked< IAIModule >( "AIModule" );

PublicDependencyModuleNames

  • List<string> PublicDependencyModuleNames:添加对执行Module的源文件依赖,自动添加所依赖Module的PublicPrivate源文件包含。

PrivateDependencyModuleNames

  • List<string> PrivateDependencyModuleNames:与PublicDependencyModuleNames不同的是,意味着所依赖的Module中的源文件只可以在Private中使用。

假如现在有一个模块A,还有一个模块B,他们中都是UE的Module/PublicModule/Private的文件结构。

  • 如果B中依赖A,如果使用的是PrivateDependencyModuleNames的方式添加的依赖,则A模块的源文件只可以在B的Private目录下的源文件中使用,在Public目录下的源文件使用时会报No such file or directory的错误。
  • 如果使用的是PublicDependencyModuleNames方式添加的依赖,则A的源文件在B的PublicPrivate中都可用。

除了上述的区别之外,还影响依赖于B模块的模块 ,当一个模块C依赖模块B的时候,只能访问到B模块的PublicDependencyModule中的模块暴露出来的类。
例如,C依赖B,B依赖A;那么,假如C想访问A中的类则有两种方式:

  1. 在C的依赖中添加上A模块
  2. 确保B在PublicDependencyModuleNames依赖中添加的A模块,这样C就可以间接的访问到A。

经过测试发现,其实对于游戏模块(PROJECT_NAME/Source/PROJECT_NAME.target.cs)使用而言,所依赖的模块是使用PublicDependencyModuleNames还是PrivateDependencyModuleNames包含,没什么区别。
使用Private方式依赖的Module中的头文件依然可以在游戏模块的Public中用,这一点与插件等其他模块有所不同(但是这只有在所依赖的模块不是bUsePrecompiled的基础上的,如果所依赖的模块是bUsePrecompiled的,则与其他的模块一样,PrivateDependencyModuleNames依赖的模块不可以在Pulibc目录下的源文件使用),这个行为比较奇怪:有时候出错有时又不出错。

注意:在游戏项目中使用依赖其他Module时尽量确定性需求地使用PrivateDependencyModuleNames或者PublicDependencyModuleNames,在组合其他的选项时可能会有一些奇怪的行为。

相关的讨论:

  1. What is the difference between PublicDependencyModuleNames and PrivateDependencyModuleNames
  2. Explanation of Source Code folder structure?

bPrecompile与bUsePrecompiled

1
2
3
4
5
6
7
8
9
/// <summary>
/// Whether this module should be precompiled. Defaults to the bPrecompile flag from the target. Clear this flag to prevent a module being precompiled.
/// </summary>
public bool bPrecompile;

/// <summary>
/// Whether this module should use precompiled data. Always true for modules created from installed assemblies.
/// </summary>
public bool bUsePrecompiled;

这个两个属性需要组合来使用。

考虑下列需求:
如果我们写好的一个模块A希望拿给别人来用,但是又不想把所有代码开放出来,该怎么办?

在传统的C++领域,我应该会说:把代码编译成DLL,然后把头文件和DLL发放给用户就可以啦。
对!其实bPrecompilebUsePrecompiled就是做的类似的事情。

当我们对模块A进行编译之前,在它的*.build.cs中添加:

1
2
3
4
5
6
7
8
9
public class A : ModuleRules
{
public A(ReadOnlyTargetRules Target) : base(Target)
{
// ...
bPrecompile=true;
// ...
}
}

然后编译模块A。编译完成之后,将模块A的Source/Private删除(删除之前请确保你已经备份),然后删除模块目录下的Intermediate,但是要保留Binaries目录。
最后,打开模块A的A.build.cs,将bPrecompile=true;删掉,然后再添加:

1
2
3
4
5
6
7
8
9
public class A : ModuleRules
{
public A(ReadOnlyTargetRules Target) : base(Target)
{
// ...
bUsePrecompiled=true;
// ...
}
}

此时我们想要实现的目标都已经完成了:不发布实现代码(Private),发布预先编译好的二进制,但是这样无法进行静态链接,如果只是暴露给蓝图使用可以,在其他的Module中使用它的符号会有符号未定义错误。

OptimizeCode(CodeOptimization)

这个属性是用来控制当前模块是否要开启优化代码,在我们用VS调试时,有时候会看到“变量已被优化,因而不可用”,这就是因为被优化了。

可以使用它来关闭优化:

1
2
// build.cs
OptimizeCode = CodeOptimization.Never;

CodeOptimization支持几种值,默认是Default,开启优化:

  • Never
  • Default
  • InNonDebugBuilds
  • InShippingBuildsOnly

相关的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// UnrealBuildTool/Configutation/UEBuildModuleCPP.cs
public static bool ShouldEnableOptimization(ModuleRules.CodeOptimization Setting, UnrealTargetConfiguration Configuration, bool bIsEngineModule)
{
switch(Setting)
{
case ModuleRules.CodeOptimization.Never:
return false;
case ModuleRules.CodeOptimization.Default:
case ModuleRules.CodeOptimization.InNonDebugBuilds:
return (Configuration == UnrealTargetConfiguration.Debug)? false : (Configuration != UnrealTargetConfiguration.DebugGame || bIsEngineModule);
case ModuleRules.CodeOptimization.InShippingBuildsOnly:
return (Configuration == UnrealTargetConfiguration.Shipping);
default:
return true;
}
}

这个函数在UEBuildModuleCPP.csCreateModuleCompileEnvironment中调用,将结果赋值给了CppCompileEnvironment.bOptimizeCode,进而又在VCToolChain.cs中被使用:

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
UnrealBuildTool\Platform\Windows\VCToolChain.cs

void AppendCLArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// other code ...

//
// Debug
//
if (CompileEnvironment.Configuration == CppConfiguration.Debug)
{
// Disable compiler optimization.
Arguments.Add("/Od");

// Favor code size (especially useful for embedded platforms).
Arguments.Add("/Os");

// Allow inline method expansion unless E&C support is requested
if (!CompileEnvironment.bSupportEditAndContinue && CompileEnvironment.bUseInlining)
{
Arguments.Add("/Ob2");
}

if ((CompileEnvironment.Platform == CppPlatform.Win32) ||
(CompileEnvironment.Platform == CppPlatform.Win64))
{
Arguments.Add("/RTCs");
}
}
//
// Development and LTCG
//
else
{
if(!CompileEnvironment.bOptimizeCode)
{
// Disable compiler optimization.
Arguments.Add("/Od");
}
else
{
// Maximum optimizations.
Arguments.Add("/Ox");

// Favor code speed.
Arguments.Add("/Ot");

// Coalesce duplicate strings
Arguments.Add("/GF");

// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if (CompileEnvironment.bOmitFramePointers == false
&& ((CompileEnvironment.Platform == CppPlatform.Win32) ||
(CompileEnvironment.Platform == CppPlatform.Win64)))
{
Arguments.Add("/Oy-");
}
}

// Allow inline method expansion
Arguments.Add("/Ob2");

//
// LTCG
//
if (CompileEnvironment.bAllowLTCG)
{
// Enable link-time code generation.
Arguments.Add("/GL");
}
}

// other code...
}

可以看到,在Debug的环境下,是默认关闭优化的。在非Debug时根据CompileEnvironment.bOptimizeCode的值来决定是否开启优化。
调试效果:
当使用默认时(OptimizeCode = CodeOptimization.Default;):

当关闭代码优化时(OptimizeCode = CodeOptimization.Never;):

建议使用OptimizeCode = CodeOptimization.InShippingBuildsOnly;

注意:这个选项和普通的C++项目在VS中的Properties-Configuration-C/C++-Optimization-Optimization的设置时一样的。

bEnableUndefinedIdentifierWarnings (bool)

是否启用在预处理代码#if中使用未定义标识符的警告。

1
#if GOOGLE_PROTOBUF_USE_UNALIGNED

如果这个宏未定义,在启用bEnableUndefinedIdentifierWarnings的情况下会产生C4688错误。

相关的代码时定义在UBT的代码中的:

1
2
3
4
5
6
7
8
9
10
11
12
// Source\Programs\UnrealBuildTool\Platform\Windows\VCToolChain.cs
if(WindowsPlatform.bUseVCCompilerArgs && CompileEnvironment.bEnableUndefinedIdentifierWarnings)
{
if (CompileEnvironment.bUndefinedIdentifierWarningsAsErrors)
{
Arguments.Add("/we4668");
}
else
{
Arguments.Add("/w44668");
}
}

bUseRTTI (bool)

UE4默认关闭了RTTI,所以在工程的代码中写了类似typeid的代码,会产生下列错误:

1
2
3
In file included from C:\UnrealProject_\Source\GWorld\Private\Modules\Flibs\FLibIniConfigHelper.cpp:10:
C:/UnrealProject_/Source/GWorld/Public/Modules/Flibs\FlibMateReflectionHelper.h(29,41): error: cannot use typeid with -fno-rtti
FString EnumTypeName = ANSI_TO_TCHAR(typeid(ENUM_TYPE).name());

解决办法只有两个:去掉rtti相关的代码,或者在当前Modulebuild.cs中把bUseRTTI设置为true

未完待续,如有谬误请不吝指正。

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

本文标题:UE Build System:Target and Module
文章作者:查利鹏
发布时间:2019年09月12日 13时14分
本文字数:本文一共有9.7k字
原始链接:https://imzlp.com/posts/16643/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!