Building a project using IL2CPP

本文深入探讨了Unity中IL2CPP的工作原理,包括如何控制生成的C++代码中的运行时检查,如null检查、数组边界检查等。同时,提供了减少构建时间和优化IL2CPP堆栈跟踪的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理:

 IL2CPP 的核心流程

IL2CPP 的工作流程可分为以下步骤:

  1. C# 代码编译为 IL
    Unity 项目中的 C# 脚本首先被编译为 .NET 中间语言(IL)(类似 Java 的字节码),存储在程序集文件(如 .dll)中。

  2. IL → C++ 代码转换
    IL2CPP 工具链读取这些 IL 代码,将其转换为 C++ 源代码(后置过程)这一步的关键在于:

    • 直接翻译逻辑:将 IL 指令逐条映射为等效的 C++ 代码。

    • 元数据保留:保留类型、方法、字段等元数据,用于运行时反射等功能。

    • 垃圾回收集成:生成与 Unity 的垃圾回收器(如 Boehm GC)兼容的代码。

  3. C++ 代码编译为原生机器码
    生成的 C++ 代码会被平台本地编译器(如 iOS 的 Xcode/Clang、Android 的 NDK/GCC)编译为 目标平台的原生二进制文件(如 ARM 或 x86 机器码)。

  4. 运行时执行
    最终生成的机器码直接由 CPU 执行,无需虚拟机或 JIT 编译,性能接近原生 C++ 程序。

与Mono的对比

1. Mono 运行时的核心流程

(1) C# 代码编译为 IL 中间语言
  • Unity 中的 C# 脚本会被编译为 .NET 中间语言(IL),存储在程序集文件(如 .dll)中。

  • 此步骤与 IL2CPP 的初始阶段相同。

(2) 运行时编译与执行

Mono 运行时根据目标平台的能力,选择以下两种模式之一:

  • JIT(Just-In-Time)编译(支持动态生成代码的平台,如 PC、Android):

    • 流程:在运行时将 IL 代码 动态编译为机器码,由 CPU 直接执行。

    • 优点:支持动态代码生成(如 System.Reflection.Emit),启动速度快。

    • 缺点:运行时编译有性能开销,且某些平台(如 iOS)禁止 JIT。

  • AOT(Ahead-Of-Time)编译(受限制的平台,如 iOS):

    • 流程:在构建阶段将 部分 IL 代码预先编译为机器码,剩余代码在运行时由轻量解释器处理。

    • 优点:绕过平台对 JIT 的限制(如 iOS 的代码签名要求)。

    • 缺点:无法编译所有 IL 代码(如泛型、反射生成的代码),可能导致运行时异常。


2. Mono 与 IL2CPP 的对比

特性Mono (JIT/AOT)IL2CPP
编译方式JIT(动态)或 AOT(部分静态)完全 AOT(静态生成 C++ → 机器码)
性能较低(JIT 运行时开销)较高(无运行时编译开销)
iOS 支持受限(AOT 不完整)完全支持
代码体积较小较大(C++ 代码膨胀)
动态代码生成支持(JIT 平台)不支持
构建时间较短较长
调试体验直接调试 C# 源码需结合生成的 C++ 代码

3. Mono 的局限性

(1) iOS 平台的 AOT 缺陷
  • 泛型实例化问题:AOT 只能为 提前已知的泛型类型组合 生成代码。例如:

    // 若未在 AOT 阶段预生成 List<int> 和 List<string> 的代码:
    var list1 = new List<int>();  // 可能正常运行(常见类型已预生成)
    var list2 = new List<MyClass>(); // 可能崩溃!
  • 反射限制:动态加载类型或方法可能失败。

(2) 性能瓶颈
  • JIT 开销:首次执行方法时的编译延迟。

  • 内存占用需同时存储 IL 和机器码

(3) 安全性
  • JIT 的动态代码生成可能被恶意利用(如 iOS 禁止 JIT 的部分安全考虑)。

(4) Mono 在 iOS 的 AOT 工作流程
  • 构建阶段

    • Unity 调用 Mono 的 AOT 编译器,将部分 IL 编译为 iOS 的 ARM 机器码

    • 生成一个静态库(如 libiPhone-arm.a)和剩余 IL 代码。

  • 运行时

    • 预编译的机器码直接执行。

    • 未预编译的代码由 Mono 解释器执行(性能极低)


4. 为什么 Unity 需要 IL2CPP?

  • 解决 Mono AOT 的缺陷:IL2CPP 通过完全静态编译,规避了泛型和反射的运行时问题。

  • 提升性能C++ 编译器优化能力远超 Mono 的 AOT。

  • 遵守平台规则:iOS 等平台禁止动态代码生成(JIT),而 IL2CPP 的完全静态编译符合要求。


5. ​​总结

  • 仅使用 Mono:依赖 JIT 或受限的 AOT 编译,适合对动态代码生成有需求的场景(如 PC 平台),但在 iOS 等受限制平台上面临兼容性和性能问题。

  • IL2CPP 是 Unity 为弥补 Mono 缺陷而引入的解决方案,通过静态编译为 C++ 代码,实现高性能和跨平台一致性。两者适用于不同场景,开发者需根据目标平台和需求权衡选择。

Unity如何构建

要在项目中使用 IL2CPP, 打开 Build Settings. 选择你要发布的平台, 点击 Player Settings 打开 Player settings属性面板

在使用IL2CPP脚本后端时,可以控制il2cpp.exe生成c++代码的方式。特别地,也可以使用下列中的c#属性用来启用或禁用runtime check。

OptionDescriptionDefault
Null checks如果这个打开(enable),则通过IL2CPP生成的代码会进行null检查,并抛出托管空引用异常 managed NullReferenceException exceptions. 如果此选项被禁用,null检查不会发送到生成的c++代码中。 对于某些项目,禁用此选项可以提高运行时性能。
但是,对生成代码中的空值的任何访问都不会被检查,并可能导致不正确的行为 通常,在空值被解除引用后,游戏很快就会崩溃, 请小心禁用此选项
Enabled
Array bounds checks如果启用此选项,由IL2CPP生成的c++代码将检查数组边界是否越界,并在必要时抛出托管的IndexOutOfRangeException异常,如果禁用此选项生成的c++代码中则不会进行数组边界检查。
同上
Enabled
Divide by zero checks除以0检查,同上,一般在写代码的时候,就会注意这一点Disable

在c#代码中,如果要开启、禁用以上option,要使用IL2CppSetOptions属性。要使用这个属性,要把Il2CppSetOptionsAttribute.cs脚本拷贝到你的asset文件夹下,在editor->data(Windows上的Data\il2cpp, OS X上的Contents/Frameworks/il2cpp)中

比如:下面没有开启nullcheck检查

[Il2CppSetOption(Option.NullChecks, false)]
public static string MethodWithNullChecksDisabled()
{
    var tmp = new object();
    return tmp.ToString();
}

可以将Il2CppSetOptions属性应用于类型、方法和属性,

[Il2CppSetOption(Option.NullChecks, false)]
public class TypeWithNullChecksDisabled
{
    public static string AnyMethod()
    {
        // Null checks will be disabled in this method.
        var tmp = new object();
        return tmp.ToString();
    }

    [Il2CppSetOption(Option.NullChecks, true)]
    public static string MethodWithNullChecksEnabled()
    {
        // Null checks will be enabled in this method.
        var tmp = new object();
        return tmp.ToString();
    }
}
public class SomeType
{
    [Il2CppSetOption(Option.NullChecks, false)]
    public string PropertyWithNullChecksDisabled
    {
        get
        {
            // Null checks will be disabled here.
            var tmp = new object();
            return tmp.ToString();
        }
        set
        {
            // Null checks will be disabled here.
            value.ToString();
        }
    }

    public string PropertyWithNullChecksDisabledOnGetterOnly
    {
        [Il2CppSetOption(Option.NullChecks, false)]
        get
        {
            // Null checks will be disabled here.
            var tmp = new object();
            return tmp.ToString();
        }
        set
        {
            // Null checks will be enabled here.
            value.ToString();
        }
    }
}

How IL2CPP works

在使用IL2CPP发布时, Unity自动执行以下步骤:

  1. 编译unity的脚本称常规的.net dll,也就是托管程序集

  2. 应用托管字节码剥离。这一步大大减少发布游戏的大小Applies managed bytecode stripping. This step significantly reduces the size of a built game.

  3. 将所有托管程序集转换为标准c++代码

  4. 使用本机平台编译器编译生成的c++代码和IL2CPP的运行时部分。.

  5. 根据您的目标平台,将代码链接到可执行文件或DLL中。

上面这张图很到位

Optimizing IL2CPP build times

当使用IL2CPP构建项目时,项目构建时间可能要长得多。然而,有几种方法可以显著减少构建时间:

使用incremental building

当使用 incremental building时,C++ 编译器仅编译相对于上一次发布改变的文件,没有改变的不编译,注意,发布的文件夹要和上一次发布的文件夹是一致的,也就是发布到同一个文件夹下

Exclude project and target build folders from anti-malware software scans

关闭杀毒软件,比如360或者Windows defender,这个会减少50-66%的时间

Store your project and target build folder on a Solid State Drive (SSD)

固态硬盘(ssd)的读/写速度比传统硬盘驱动器(HDD)更快。将IL代码转换为c++并编译它涉及到大量的读/写操作。更快的存储设备加速了这一过程。

Managed stack traces with IL2CPP

当托管代码中发生异常时,  stack trace 会告诉你发生错误的地方. 然而它并不总是出现,这时候,就需要配置的il2cpp的错误堆栈, 下面分别是c#自带的和c++配置的

Debug builds

当使用 debug 模式构建时,会打印出错误的堆栈信息

Release builds

当使用 release 模式构建时,IL2CPP 可能会产生缺少一个或多个托管方法的调用堆栈。这是因为C++编译器内联了缺失的方法。方法内联通常对运行时的性能有好处,但它可能使调用堆栈更难以理解。

要使用命令行对Unity项目进行Il2Cpp编译,首先需要使用Unity编辑器导出Android Export Project。请按照以下步骤操作: 1. 在Unity编辑器中打开您的项目,并确保已经选择了Android平台。 2. 在菜单栏中转到 File -> Build Settings。 3. 在 Build Settings 窗口中,选择 Android 平台并点击 “Export” 按钮。 4. 在弹出的“Export Project”对话框中,输入导出的项目路径和名称,并勾选“Export Google Android Project”选项。 5. 单击“Export”按钮以开始导出项目。完成后,您将在指定的目录中找到导出的Android项目。 一旦您已成功导出了Android项目,就可以使用命令行执行Il2Cpp编译。请按照以下步骤操作: 1. 打开命令提示符或终端,并导航到导出的Android项目的根目录。 2. 输入以下命令以生成Il2Cpp头文件: ``` Unity.exe -batchmode -nographics -silent-crashes -logFile unity.log -projectPath "path/to/project" -executeMethod Unity.IL2CPP.Building.BuilderUtils.RunIl2CppHeaderGenerator -buildTarget android ``` 请注意,此处 `Unity.exe` 应该替换为您在本地计算机上的Unity编辑器路径。 3. 一旦生成了Il2Cpp头文件,就可以使用以下命令执行Il2Cpp编译: ``` Unity.exe -batchmode -nographics -silent-crashes -logFile unity.log -projectPath "path/to/project" -executeMethod Unity.IL2CPP.Building.BuilderUtils.RunIl2Cpp -buildTarget android ``` 与上面一样,`Unity.exe` 应该替换为您本地计算机上的Unity编辑器路径。 4. 执行完毕后,您将在项目的“Temp/StagingArea/Il2Cpp”目录中找到编译后的二进制文件。 请注意,如果您使用的是Unity 2019或更高版本,则可以使用 `- il2cppCompilerConfiguration` 命令行参数指定Il2Cpp编译器配置文件。例如: ``` Unity.exe -batchmode -nographics -silent-crashes -logFile unity.log -projectPath "path/to/project" -executeMethod Unity.IL2CPP.Building.BuilderUtils.RunIl2Cpp -buildTarget android -il2cppCompilerConfiguration [path/to/il2cpp_config_folder]/il2cpp_config_android_arm64.xml ``` 希望这些信息能对您有所帮助!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TO_ZRG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值