Unity IL2CPP构建环境下使用protobuf-net序列化特定泛型Dictionary类型时触发ExecutionEngineException异常
你遇到的这个问题核心是Unity IL2CPP的AOT(提前编译)特性和protobuf-net的泛型序列化机制之间的冲突:
IL2CPP要求所有在运行时需要使用的代码必须在编译阶段就生成,而protobuf-net的MapDecorator(字典类型的序列化器)是泛型类,针对Dictionary<T,U>的不同键值类型组合,会动态生成对应的序列化器实例。对于Dictionary<string, string>,大概率是protobuf-net内部对字符串键值对做了特殊的AOT兼容处理,或者Unity IL2CPP默认保留了字符串相关的泛型代码路径,所以能正常工作;但像string-int、string-float这类值类型组合,对应的MapDecorator静态构造函数(.cctor)没有被IL2CPP提前生成代码,导致运行时调用时抛出异常。
再加上你使用的是protobuf-net 2.4.6版本(v3确实有IL2CPP兼容问题),这个版本的AOT支持需要显式告知编译器哪些泛型类型需要提前处理。
以下是按优先级排序的解决方法,从最简单到最彻底:
1. 显式预准备序列化器(最快见效)
在游戏启动阶段,提前调用Serializer.PrepareSerializer<T>()方法,强制protobuf-net初始化你需要的所有泛型类型的序列化器,让IL2CPP在编译时生成对应的AOT代码。
修改你的Entry类,添加Awake方法:
void Awake() { // 提前准备所有需要序列化的DataDictionnary泛型版本 Serializer.PrepareSerializer<DataDictionnary<string, int>>(); Serializer.PrepareSerializer<DataDictionnary<string, float>>(); Serializer.PrepareSerializer<DataDictionnary<int, int>>(); Serializer.PrepareSerializer<DataDictionnary<bool, string>>(); }
这个方法会触发protobuf-net为指定类型生成序列化器的所有必要代码,IL2CPP会把这些代码纳入编译范围,解决运行时缺失的问题。
2. 优化Linker配置(辅助加固)
虽然你已经配置了link.xml保留protobuf-net程序集,但可以更精确地指定保留字典序列化器的泛型类型,避免被Unity的Linker误剥离关键代码。更新你的link.xml:
<linker> <assembly fullname="Test.Serialization" preserve="all"/> <assembly fullname="protobuf-net" preserve="all"/> <!-- 明确保留MapDecorator泛型类的所有版本 --> <type fullname="ProtoBuf.Serializers.MapDecorator`3" preserve="all" /> </linker>
注意:这只是辅助手段,核心解决还是靠PrepareSerializer,因为IL2CPP的AOT问题是代码生成,不是代码剥离。
3. 使用protobuf-net AOT预生成工具(最彻底)
protobuf-net提供了预生成序列化代码的工具,可以直接为你的DataDictionnary的各种泛型版本生成静态C#序列化代码,完全避免运行时反射生成的问题:
- 在一个单独的.NET工具项目中安装
protobuf-net.ToolsNuGet包 - 使用命令行工具
protogen生成序列化代码,示例命令:protogen -i:YourDataModel.cs -o:GeneratedSerializers.cs -t:protobuf-net - 把生成的
GeneratedSerializers.cs文件添加到Unity项目中,IL2CPP会直接编译这些静态代码,彻底解决AOT兼容问题。
4. 改用具体子类(备选方案)
如果泛型的AOT问题始终难以调试,可以为每个需要的字典类型创建具体的子类,让IL2CPP明确识别并生成对应的序列化代码:
[ProtoContract] public class DataDictionaryStringInt : DataDictionnary<string, int> { } [ProtoContract] public class DataDictionaryStringFloat : DataDictionnary<string, float> { }
然后在测试代码中使用这些子类替代泛型类,比如:
var data = new DataDictionaryStringInt(); data.AddValue("plop", _a); SerializationTests.AttemptSerializingData(data, displayProto, this);
修改完成后,切换到IL2CPP构建模式,重新构建并运行游戏,测试各个按钮的序列化功能,应该不会再触发ExecutionEngineException异常。
内容的提问来源于stack exchange,提问作者Infenix




