脚本限制
Understand the restrictions that apply on some AOT platforms when using IL2CPP as your scripting back end.
阅读时间8 分钟最后更新于 1 个月前
Unity 在支持的所有平台之间提供通用的脚本 API 和体验。但是,有些平台存在固有的限制。为帮助您了解这些限制,下表描述了每个平台和脚本后端适用的限制:
平台(脚本后端) | 提前编译 | 支持线程 |
|---|---|---|
| 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 编译器无法检测到需要为该类型或方法生成代码。
通用类型和方法
对于泛型类型和方法,编译器必须确定哪些泛型实例被使用,因为不同的泛型实例可能需要不同的代码。例如, 的代码与 的代码是不同的。但是,IL2CPP 将共享引用类型的用法代码,因此 和 会使用相同的代码。
List<int>List<double>List<object>List<string>在以下情况下可以引用 IL2CPP 未找到编译时间的通用类型和方法:
- 在运行时创建新的泛型实例:
Activator.CreateInstance(typeof(SomeGenericType<>).MakeGenericType(someType)); - 在泛型实例上调用静态方法:
typeof(SomeGenericType<>).MakeGenericType(someType)).GetMethod(“AMethod”).Invoke(null, null); - 调用静态泛型方法:
typeof(SomeType).GetMethod(“GenericMethod”).MakeGenericMethod(someType).Invoke(null, null); - 对在编译时无法推断的泛型虚函数的某些调用。
- 具有深度嵌套的泛型值类型(例如 )的调用。
Struct<Struct<Struct<...<Struct<int>>>>
为了支持这些情况,IL2CPP 会生成可用于任何类型参数的泛型代码。但是,此代码的速度较慢,因为它无法假设类型的大小,也无法判断它是引用类型还是值类型。如果需要确保生成更快的泛型方法,请执行以下操作:
- 如果泛型参数始终为引用类型,请添加 限定符。然后,IL2CPP 将使用引用类型共享生成回退方法,这样不会导致性能下降。
where: class - 如果泛型参数始终为值类型,请添加 限定符。这样可以启用一些优化,但代码仍然会较慢,因为值类型的大小可能不同。
where: struct - 创建一个名为 的方法,并添加您希望 IL2CPP 生成的泛型类型和方法的引用。此方法不需要被调用(而且可能不应该被调用)。以下示例将确保为
UsedOnlyForAOTCodeGeneration生成一个特化版本。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.");}
请注意,启用“Faster (smaller) build”设置时,仅编译单个完全可共享的泛型代码版本。这会减少生成的方法数量,减少编译时间和构建大小,但会以牺牲运行时性能为代价。
从原生代码调用托管方法
需要编组到 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;}
无线程
有些平台不支持使用线程,因此任何使用 命名空间的托管代码都将在运行时失败。此外,.NET 类库的某些部分存在对线程的隐式依赖。一个常用的例子是 类,它依赖于对线程的支持。
System.ThreadingSystem.Timers.Timer异常过滤器
IL2CPP 支持异常过滤器,但由于 IL2CPP 使用 C++ 异常来实现托管异常,过滤器语句和 catch 块的执行顺序不同。除非过滤器阻止写入字段,否则这一点不会引起注意。
MarshalAs 和 FieldOffset 属性
动态关键字
IL2CPP 不支持 C# 关键字。此关键字需要 JIT 编译,而 IL2CPP 无法实现。
dynamicMarshal.Prelink
IL2CPP 不支持 或 API 方法。
Marshal.PrelinkMarshal.PrelinkAllSystem.Diagnostics.Process API
IL2CPP 不支持 API 方法。对于桌面平台需要此脚本的情况,请使用 Mono 脚本后端。
System.Diagnostics.Process