版本: 2022.3
语言: 英语
使用IL2CPP管理堆栈跟踪
管理代码剥离

脚本限制

Unity为其支持的各个平台提供统一的脚本API和体验。然而,某些平台存在固有的限制。为了帮助您理解这些限制,以下表格描述了各个平台适用的限制以及 脚本后端一个支持Unity脚本的框架。Unity根据目标平台支持三种不同的脚本后端:Mono、.NET和IL2CPP。但是,通用Windows平台只支持两种:.NET和IL2CPP。《了解更多信息》(更多信息)
术语表中查看

平台(脚本后端) 编译前 支持线程
Android(IL2CPP)
Android(Mono)
iOS(IL2CPP)
独立(IL2CPP)
独立(Mono)
通用Windows平台支持微软In App Purchase模拟器的IAP功能,允许您在发布应用程序之前在设备上测试IAP购买流程。《更多信息》(更多信息)
术语表中查看
(IL2CPP)
WebGL在Web浏览器中渲染2D和3D图形的JavaScript API。Unity WebGL构建选项允许Unity将内容发布为JavaScript程序,这些程序使用HTML5技术和WebGL渲染API在Web浏览器中运行Unity内容。《更多信息》(更多信息)
术语表中查看
(IL2CPP)

编译前(AOT)

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

反射

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

System.Reflection.Emit

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

序列化

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

泛型类型和方法

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

在以下情况下,可能无法引用IL2CPP找不到编译时的泛型类型和方法

  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>>>

为了支持这些情况,IL2CPP会生成可以与任何类型参数一起工作的泛型代码。然而这种代码运行速度较慢,因为它无法对类型的尺寸或者它是一个引用类型还是值类型做出假设。如果您需要确保生成更快的泛型方法,可按以下步骤操作

  • 如果泛型参数始终是引用类型,则添加 where: class 约束。然后IL2CPP将使用引用类型共享生成后备方法,这不会引起性能下降。
  • 如果泛型参数始终是值类型,请添加 where: struct 约束。这使一些优化成为可能,但由于值类型的尺寸可能不同,代码仍然会比较慢。
  • 创建一个名为 UsedOnlyForAOTCodeGeneration 的方法,并将您希望IL2CPP生成的泛型类型和方法引用添加到其中。此方法不需要(可能也不应该)被调用。下面的例子将确保为 GenericType 生成一个特定的实现。
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.");
}

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

从原生代码调用托管方法

需要被 marshaled 到 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 支持异常过滤器,但由于 IL2CPP 使用 C++ 异常来实现托管异常,因此执行过滤器语句和 catch 块的顺序有所不同。这在不阻挡字段写入的情况下不会引起注意。

MarshalAs 和 FieldOffset 属性

IL2CPP 不支持在运行时反射 MarshalAsFieldOffset 属性。它支持这些属性在编译时。您应使用这些属性进行适当的 平台调用 marshaling

dynamic 关键字

IL2CPP 不支持 C# 的 dynamic 关键字。该关键字需要 JIT 编译,而 IL2CPP 不支持 JIT 编译。

Marshal.Prelink

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

System.Diagnostics.Process API

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

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