点击任意处关闭

【IL2CPP】解决反序列化对象时的 AOT 错误


我在做 MinecraftClone 项目时,需要使用 Newtonsoft.Json 反序列化一些对象。这在编辑器里运行正常,但我用 IL2CPP 构建了这个项目以后,每次运行都会报错。

下面是错误信息:

Error Messages
ExecutionEngineException: Attempting to call method 'System.Collections.Generic.List`1[[System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]::.cctor' for which no ahead of time (AOT) code was generated.

比较神奇的是反序列化 List<int?> 会报错,但反序列化 List<string> 就不会报错。

解决方案

我在 Newtonsoft.Json 的 Issues 里找到了解决方案:

The issue here is about some AOT generation. Funny, System.Collections.Generic.List<T> is one of the more difficult types to get an AOT error about :)

I have written some wiki pages, a bit hidden, over here: https://github.com/jilleJr/Newtonsoft.Json-for-Unity/wiki/Fix-AOT-compilation-errors, and in this case, I would suggest looking into using AotHelper. Especially the AotHelper.EnsureList<T>

To fix this, you need to add AotHelper.EnsureList<NestedStruct>() to a file that won't be stripped (such as a class that inherits from MonoBehaviour, a type with the [Preserve] attribute, or a type that's preserved via a link.xml file). The code does not need to run, nor does it need to be added to a GameObject in a scene; it only need to be included in the build.
[1]

这是我写的代码:

C#
using Newtonsoft.Json.Utilities; using UnityEngine.Scripting; [Preserve] public class AOTUtils { public void EnsureAOT() { AotHelper.EnsureList<int?>(); } }

OK!问题解决。

深究原因

For reference, the reason it works when it's a class and not when it's a struct is because the IL2CPP compiler does some generics reuse in its code generation.

The Unity engine already has some references to some List<T> types here and there, and the IL2CPP compiler can take the code it generates for example for a List<GameObject> and reuse the exact same implementation for List<NestedClass>. This is because both GameObject and NestedClass in this example are reference types. I.e. they are just pointers. It doesn't matter if one's a pointer to a GameObject and one's to a NestedClass, they both are just the same size of a .NET pointer.

In the background, a List<T> stores its items in an array of T[]. To restate: GameObject[] and NestedClass[] arrays both look the exact same in memory, and IL2CPP can abuse this. Example memory of such array:

Memory Layout
0: int32, array length (4 in this case) 1: int64, pointer to GameObject at index 0 2: int64, pointer to GameObject at index 1 3: int64, pointer to GameObject at index 2 4: int64, pointer to GameObject at index 3
Memory Layout
0: int32, array length (4 in this case) 1: int64, pointer to NestedClass at index 0 2: int64, pointer to NestedClass at index 1 3: int64, pointer to NestedClass at index 2 4: int64, pointer to NestedClass at index 3

Structs have this particular property of being embedded wherever they're used. For example, if we had the following struct:

C#
struct Vector2 { public int x; public int y; }

Then the memory of an Vector2[] would look like so:

Memory Layout
0: int32, array length (4 in this case) 1: int32, Vector2.x of Vector2 at index 0 2: int32, Vector2.y of Vector2 at index 0 3: int32, Vector2.x of Vector2 at index 1 4: int32, Vector2.y of Vector2 at index 1 5: int32, Vector2.x of Vector2 at index 2 6: int32, Vector2.y of Vector2 at index 2 7: int32, Vector2.x of Vector2 at index 3 8: int32, Vector2.y of Vector2 at index 3

Because structs behaves like this, IL2CPP does not even try to reuse generic types where the generic type argument is a struct.
[2]

由于引用类型变量或实参的本质都是一个 .NET 对象指针,所以操作它们的方式都是一样的,那么为它们生成的代码就都可以共享。例如,List<Stream> 的方法编译的代码可以直接用于 List<string> 的对应方法。

对于 List<int?> 来说,它的类型参数是值类型,代码必须要单独生成。因为我没有在代码中显式地创建 List<int?> 对象,所以在 IL2CPP 裁剪代码时,把 List<int?>::.cctor(静态构造函数)等部分代码给裁剪掉了。

AotHelper.EnsureList<int?>() 这个方法内部显式调用了 List<int?> 的构造函数。只要调用这个方法的语句不被裁剪,即使它不被真正执行,IL2CPP 也会保留 List<int?> 的相应代码,文章开头提到的问题也就被巧妙地解决了。


  1. Bug: [IL2CPP] ExecutionEngineException on deserializing array of struct #77 (https://github.com/jilleJr/Newtonsoft.Json-for-Unity/issues/77#issuecomment-766592006) ↩︎

  2. Bug: [IL2CPP] ExecutionEngineException on deserializing array of struct #77 (https://github.com/jilleJr/Newtonsoft.Json-for-Unity/issues/77#issuecomment-766598863) ↩︎


Title
Subtitle
00:00 / 00:00
播放列表