版本:Unity 6 (6000.0)
语言 : 英语
使用 IL2CPP 的托管堆栈跟踪
托管代码剥离

脚本限制

Unity 在其支持的所有平台上提供通用的脚本 API 和体验。但是,某些平台具有固有的限制。为了帮助您了解这些限制,下表描述了哪些限制适用于每个平台和 脚本后端Unity 中支持脚本的框架。根据目标平台,Unity 支持三种不同的脚本后端:Mono、.NET 和 IL2CPP。但是,通用 Windows 平台只支持两种:.NET 和 IL2CPP。 更多信息
参见 术语表

平台(脚本后端) 提前编译 支持线程
Android (IL2CPP)
Android (Mono)
iOS (IL2CPP)
独立 (IL2CPP)
独立 (Mono)
通用 Windows 平台 (IL2CPP)
Web (IL2CPP)

提前编译 (AOT)

某些平台不允许运行时代码生成。任何依赖于目标设备上即时 (JIT) 编译的托管代码都将失败。相反,您必须提前 (AOT) 编译所有托管代码。通常,这种区别并不重要,但在某些特定情况下,AOT 平台需要额外考虑。

反射

Unity 在 AOT 平台上支持反射。但是,如果编译器无法推断出代码是通过反射使用的,则代码可能在运行时不存在。有关更多信息,请参阅 托管代码剥离

System.Reflection.Emit

AOT 平台无法实现 System.Reflection.Emit 命名空间中的任何方法。

序列化

由于使用反射,AOT 平台可能会遇到序列化和反序列化问题。如果类型或方法仅通过反射用作序列化或反序列化的部分,则 AOT 编译器无法检测到它需要为该类型或方法生成代码。

泛型类型和方法

对于泛型类型和方法,编译器必须确定使用哪些泛型实例,因为不同的泛型实例可能需要不同的代码。例如,List<int> 的代码与 List<double> 的代码不同。但是,IL2CPP 将共享引用类型使用的代码,因此 List<object>List<string> 将使用相同的代码。

可以引用 IL2CPPUnity 开发的脚本后端,可以作为在为某些平台构建项目时使用 Mono 的替代方案。 更多信息
参见 术语表
在以下情况下未找到编译时的泛型类型和方法

  1. 在运行时创建新的泛型实例:Activator.CreateInstance(typeof(SomeGenericType<>).MakeGenericType(someType));
  2. 调用泛型实例上的静态方法:typeof(SomeGenericType<>).MakeGenericType(someType)).GetMethod(“AMethod”).Invoke(null, null);
  3. 调用静态泛型方法:typeof(SomeType).GetMethod(“GenericMethod”).MakeGenericMethod(someType).Invoke(null, null);
  4. 对编译时无法推断的泛型虚拟函数的某些调用。
  5. 使用深度嵌套的泛型值类型进行的调用,例如 Struct<Struct<Struct<...<Struct<int>>>>

为了支持这些情况,IL2CPP 生成适用于任何类型参数的泛型代码。但是,此代码速度较慢,因为它无法对类型的大小或其是引用类型还是值类型做出任何假设。如果您需要确保生成更快的泛型方法,请执行以下操作

  • 如果泛型参数始终是引用类型,请添加 where: class 约束。然后,IL2CPP 将使用引用类型共享生成回退方法,不会导致性能下降。
  • 如果泛型参数始终是值类型,请添加 where: struct 约束。这会启用一些优化,但代码仍然会更慢,因为值类型的大小可能不同。
  • 创建一个名为 UsedOnlyForAOTCodeGeneration 的方法,并添加对希望 IL2CPP 生成的泛型类型和方法的引用。此方法不需要(也不应该)被调用。下面的示例将确保生成 GenericType<MyStruct> 的专用化。
public void UsedOnlyForAOTCodeGeneration()
{
    // Ensure that IL2CPP will create code for MyGenericStruct
    // using MyStruct as an argument.
    new GenericType<MyStruct>();

    // Ensure that IL2CPP will create code for SomeType.GenericMethod
    // using MyStruct as an argument.
    new SomeType().GenericMethod<MyStruct>();

    public void OnMessage<T>(T value) 
    {
        Debug.LogFormat("Message value: {0}", value);
    }

    // Include an exception so we can be sure to know if this
    // method is ever called.
    throw new InvalidOperationException(
        "This method is used for AOT code generation only. " +
        "Do not call it at runtime.");
}

请注意,当启用“更快(更小)的构建”设置时,只编译泛型代码的单个完全可共享版本。这减少了生成的方法数量,从而减少了编译时间和构建大小,但以牺牲运行时性能为代价。

从本机代码调用托管方法

需要被封送到 C 函数指针以便从本机代码调用的托管方法在 AOT 平台上有一些限制

  • 托管方法必须是静态方法
  • 托管方法必须具有 [MonoPInvokeCallback] 属性
  • 如果托管方法是泛型的,则可能需要使用 [MonoPInvokeCallback(Type)] 重载来指定需要支持的泛型专用化。如果是这样,该类型必须是具有正确泛型参数数量的泛型实例。可以在一个方法上有多个 [MonoPInvokeCallback] 属性,如下所示
// Generates reverse P/Invoke wrappers for NameOf<long> and NameOf<int>
// Note that the types are only used to indicate the generic arguments.
[MonoPInvokeCallback(typeof(Action<long>))]
[MonoPInvokeCallback(typeof(Action<int>))]
private static string NameOfT<T>(T item) 
{
    return typeof(T).Name;
}

没有线程

某些平台不支持使用线程,因此任何使用 System.Threading 命名空间的托管代码都将在运行时失败。此外,.NET 类库的某些部分隐式地依赖于线程。一个经常使用的例子是 System.Timers.Timer 类,它依赖于对线程的支持。

异常筛选器

IL2CPP 支持异常筛选器,但是过滤器语句和 catch 块的执行顺序不同,因为 IL2CPP 使用 C++ 异常来实现托管异常。除非过滤器阻止对字段的写入,否则您不会注意到这一点。

MarshalAs 和 FieldOffset 属性

IL2CPP 在运行时不支持对 MarshalAsFieldOffset 属性的反射。它在编译时支持这些属性。您应该使用这些属性来进行正确的 平台调用封送

dynamic 关键字

IL2CPP 不支持 C# dynamic 关键字。此关键字需要 JIT 编译,这在 IL2CPP 中是不可能的。

Marshal.Prelink

IL2CPP 不支持 Marshal.PrelinkMarshal.PrelinkAll API 方法。

System.Diagnostics.Process API

IL2CPP 不支持 System.Diagnostics.Process API 方法。对于在桌面平台上需要此功能的情况,请使用 Mono 脚本后端。

其他资源

使用 IL2CPP 的托管堆栈跟踪
托管代码剥离