文档

Unity Engine

Unity Engine

Unity 链接器

Understand how the Unity linker analyzes and marks elements of your code before stripping.
阅读时间13 分钟最后更新于 1 个月前

Unity 构建过程使用称为 Unity 链接器 (Unity linker) 的工具剥离托管代码。Unity 链接器是专为与 Unity 配合使用的 IL Linker 版本。Unity 链接器的自定义 Unity 引擎特定部分未公开使用。
Unity 链接器负责托管代码剥离和引擎代码剥离过程的一部分,这是通过 IL2CPP 脚本后端实现的单独过程,用于删除未使用的引擎代码。有关更多信息,请参阅 PlayerSettings.StripEngineCode。

Unity 链接器的工作原理

Unity 链接器将分析项目中的所有程序集。首先,它会标记根类型、方法、属性和字段。例如,添加到场景 (Scene) 中游戏对象的 MonoBehaviour 派生类是根类型。
然后,Unity 链接器分析已标记为要进行识别的根,并标记这些根所依赖的托管代码。完成此静态分析后,所有剩余的未标记代码都无法通过应用程序代码中的任何执行路径来访问,Unity 链接器将从程序集中将其删除。

Unity 链接器剥离程序集的方式

Unity 编辑器会创建包含了 Unity 项目任何场景所使用类型的程序集列表,并将其传递给 Unity 链接器。然后,Unity 链接器会处理这些程序集、这些程序集的任何引用、link.xml 文件中声明的任何程序集以及具有 AlwaysLinkAssembly 特性的任何程序集。通常,Unity 链接器不会处理项目中包含的不属于这些类别的程序集,也不会将它们排除在播放器构建之外。
Unity 链接器在处理每个程序集时遵循一组规则,这组规则基于程序集分类、程序集是否包含场景中使用的类型以及为构建选择的托管剥离级别 (Managed Stripping Level)。
根据这些规则的用途,程序集划为以下几种分类:
  • .NET 类库程序集 - 包括 Mono 类库(如 mscorlib.dll 和 System.dll)以及 .NET 类库外观程序集(如 netstandard.dll)。
  • 平台 SDK 程序集 - 包括特定于平台 SDK 的托管程序集。例如,通用 Windows 平台 SDK 中包含的 windows.winmd 程序集。
  • Unity 引擎模块程序集 - 包括构成 Unity 引擎的托管程序集,如 UnityEngine.Core.dll。
  • 项目程序集 - 包括特定于项目的程序集,例如:

标记规则

在 Unity 中构建项目时,构建过程将 C# 代码编译为称为公共中间语言 (CIL) 的 .NET 字节码格式。Unity 会将此 CIL 字节码打包到称为程序集的文件中。.NET 框架库以及在项目中使用的插件中的所有 C# 库也都会预先打包为 CIL 字节码的程序集。
在 Unity 链接器执行静态分析时,它会遵循规则集来确定 Unity 链接器会将 CIL 字节码的哪些部分标记为构建所需的部分。根标记规则决定了 Unity 链接器如何在构建中识别和保留顶级程序集。依赖关系标记规则决定了 Unity 链接器如何识别和保留根程序集所依赖的任何代码。
Managed Stripping Level 属性会更改 Unity 链接器使用的规则集。以下部分将介绍 Managed Stripping Level 属性的每个可能设置的标记规则。

根标记规则

下表介绍了 Unity 链接器如何识别程序集中的顶级类型:

程序集类型:

标记规则:

最低低中高
.NET 类和平台 SDK 以及 UnityEngine 程序集采用预防性保留以及任何 link.xml 文件中定义的任何保留。采用预防性保留以及任何 link.xml 文件中定义的任何保留。采用任何 link.xml 文件中定义的任何保留。采用任何 link.xml 文件中定义的任何保留。
包含场景中引用的类型的程序集标记程序集中的所有类型和成员。标记程序集中的所有类型和成员。标记以下内容:
• 具有
[RuntimeInitializeOnLoadMethod]
或
[Preserve]
特性的所有方法。
• 任何 link.xml 文件中定义的保留。
• 在预编译程序集、软件包程序集、Unity 脚本程序集或程序集定义程序集中标记派生自 MonoBehaviour 和 ScriptableObject 的所有类型。
标记以下内容:
• 具有
[RuntimeInitializeOnLoadMethod]
或
[Preserve]
特性的所有方法。
• 任何 link.xml 文件中定义的保留。
• 在预编译程序集、软件包程序集、Unity 脚本程序集或程序集定义程序集中标记派生自 MonoBehaviour 和 ScriptableObject 的所有类型。
其他全部标记程序集中的所有类型和成员。标记以下内容:
• 所有公共类型及其公共成员。
• 具有
[RuntimeInitializeOnLoadMethod]
或
[Preserve]
特性的所有方法。
• 任何 link.xml 文件中定义的保留。
• 预编译程序集、软件包程序集、Unity 脚本程序集或程序集定义程序集中派生自 MonoBehaviour 和 ScriptableObject 的所有类型。
标记以下内容:
• 所有公共类型及其公共成员。
• 具有
[RuntimeInitializeOnLoadMethod]
或
[Preserve]
特性的所有方法。
• 任何 link.xml 文件中定义的保留。
• 预编译程序集、软件包程序集、Unity 脚本程序集或程序集定义程序集中派生自 MonoBehaviour 和 ScriptableObject 的所有类型。
标记以下内容:
• 具有
[RuntimeInitializeOnLoadMethod]
或
[Preserve]
特性的所有方法。
• 任何 link.xml 文件中定义的保留。
• 预编译程序集、软件包程序集、Unity 脚本程序集或程序集定义程序集中派生自 MonoBehaviour 和 ScriptableObject 的所有类型。
测试标记具有
[UnityTest]
特性的任何方法以及使用 NUnit.Framework 中定义的特性进行注释的任何方法。

依赖关系标记规则

Unity 链接器识别程序集的根后,它需要识别这些根所依赖的任何代码。下表介绍了 Unity 链接器如何识别程序集内根类型的依赖关系:

规则目标

每个剥离级别的操作

最低低中高
MonoBehaviourUnity 链接器在标记 MonoBehavior 类型时标记该类型的所有成员。
ScriptableObjectUnity 链接器在标记 ScriptableObject 类型时标记该类型的所有成员。
特性Unity 链接器标记程序集、类型或其他代码结构时,也会标记这些结构的所有特性。Unity 链接器标记程序集、类型或其他代码结构时,也会标记这些结构的所有特性。Unity 链接器标记程序集、类型或其他代码结构时,仅当特性类型也被标记时,才会标记这些结构的特性。Unity 链接器标记程序集、类型或其他代码结构时,仅当特性类型也被标记时,才会标记这些结构的特性。
调试特性启用脚本调试后,Unity 链接器会标记具有
[DebuggerDisplay]
特性的所有成员,即使没有使用这些成员的代码路径时也是如此。
启用脚本调试后,Unity 链接器会标记具有
[DebuggerDisplay]
特性的所有成员,即使没有使用这些成员的代码路径时也是如此。
Unity 链接器会始终删除调试特性,如 DebuggerDisplayAttribute 和 DebuggerTypeProxyAttribute。Unity 链接器会始终删除调试特性,如 DebuggerDisplayAttribute 和 DebuggerTypeProxyAttribute。
.NET 外观类库在运行时不需要外观程序集,因此会删除它们。
Link.xml
文件支持不经常使用的“features”XML 属性。在示例中,
mscorlib.dll
中嵌入的
mscorlib.xml
文件使用此特性,但在适当情况下,您可以在任何
link.xml
文件中使用该特性。
在使用高剥离级别时,Unity 链接器根据当前构建的设置来排除对于不受支持功能的保留:
  1. remoting - 以 IL2CPP 脚本后端作为目标时排除。
  2. sre - 以 IL2CPP 脚本后端作为目标时排除。
  3. com - 以不支持 COM 的平台作为目标时排除。
例如,下面的 link.xml 文件在支持 COM 的平台上保留某种类型的一个方法,并在所有平台上保留一个方法:
<linker> <assembly fullname="Foo"> <type fullname="Type1"> <!--Preserve FeatureOne on platforms that support COM--> <method signature="System.Void FeatureOne()" feature="com"/> <!--Preserve FeatureTwo on all platforms--> <method signature="System.Void FeatureTwo()"/> </type> </assembly></linker>

方法体的编辑

在使用高剥离级别时,Unity 链接器将编辑方法体,从而进一步减小代码大小。本节总结了 Unity 链接器对方法体所做出的一些值得注意的编辑。
Unity 链接器目前只编辑 .NET 类库程序集中的方法体。在方法体编辑之后,程序集的源代码不再与程序集中经过编译的代码相匹配,因此可能会使调试变得更加困难。
以下列表介绍了 Unity 链接器用于编辑方法体时可以执行的操作:
  • 删除无法访问的分支 - Unity 链接器会删除用于检查 System.Environment.OSVersion.Platform 并且当前目标平台无法访问的 If 语句块。
  • 仅访问字段的内联方法 - 对于获取或设置字段的方法调用,Unity 链接器会替换为直接字段访问。这种做法通常能够完全剥离方法。在使用 Mono 后端时,仅当方法的调用者获准直接访问字段(根据字段的可见性而定),Unity 链接器才会进行此更改。对于 IL2CPP,可见性规则不适用,因此 Unity 链接器会在适当情况下均进行此更改。
  • 返回常量值的内联方法 - Unity 链接器对仅返回常量值的方法进行调用内联。
  • 删除空的非返回调用 - Unity 链接器会删除内容为空以及返回类型为 void 的方法调用。
  • 删除空作用域 - Unity 链接器会在 Finally 代码块为空时删除 Try/Finally 代码块。删除空调用可能会产生空的 Finally 代码块。在方法编辑过程中发生这种情况时,Unity 链接器将删除整个 Try/Finally 代码块。可能发生这种情况的一个情形是编译器在 Foreach 循环中生成 Try/Finally 代码块来调用 Dispose()。

其他资源