I am trying to make protobuf-net work with Unity. From the lot of research I've done the last days, it seems to be complicated to get both to work fine together. I have right now a problem I can't get around, when building using IL2CPP. The serialization appears to sometimes fail for certain types, like Dictionary<string, int> or Dictionary<string, float> but works fine for Dictionary<string, string>. To make things clear, I'm using protobuf-net version 2.4.6 from nuget as it seems that v3 isn't supported at all from unity (unimplemented IL2CPP methods). The occurring error is:
ExecutionEngineException: Attempting to call method 'ProtoBuf.Serializers.MapDecorator`3[[System.Collections.Generic.Dictionary`2[[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]::.cctor' for which no ahead of time (AOT) code was generated.
at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0
at System.Reflection.MonoCMethod.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <00000000000000000000000000000000>:0
at System.Reflection.MonoCMethod.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <00000000000000000000000000000000>:0
at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.ValueMember.BuildSerializer () [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.ValueMember.get_Serializer () [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.MetaType.BuildSerializer () [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.MetaType.get_Serializer () [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.RuntimeTypeModel.Serialize (System.Int32 key, System.Object value, ProtoBuf.ProtoWriter dest) [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.TypeModel.SerializeCore (ProtoBuf.ProtoWriter writer, System.Object value) [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.TypeModel.Serialize (System.IO.Stream dest, System.Object value, ProtoBuf.SerializationContext context) [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Meta.TypeModel.Serialize (System.IO.Stream dest, System.Object value) [0x00000] in <00000000000000000000000000000000>:0
at ProtoBuf.Serializer.Serialize[T] (System.IO.Stream destination, T instance) [0x00000] in <00000000000000000000000000000000>:0
at SerializationTests.SerializeObject[T] (T objectToSave) [0x00000] in <00000000000000000000000000000000>:0
at SerializationTests.AttemptSerializingData[T] (T dataToSerialize, Entry entry) [0x00000] in <00000000000000000000000000000000>:0
at Entry.OnGUI () [0x00000] in <00000000000000000000000000000000>:0
The class I'm trying to serialize on a side project to test looks like this:
using System.Collections.Generic;
using ProtoBuf;
[ProtoContract]
public class DataDictionnary<T, U>
{
[ProtoMember(1)] private readonly Dictionary<T, U> someDictionnary = new Dictionary<T, U>();
public DataDictionnary()
{
}
public void AddValue(T key, U value)
{
if (someDictionnary.ContainsKey(key)) return;
someDictionnary.Add(key, value);
}
public override string ToString()
{
string s = $"{GetType()}";
foreach (var key in someDictionnary.Keys)
{
s += $" ({key}, {someDictionnary[key]})";
}
return s;
}
}
Is there a reason I can serialize only certain types of Dictionaries with primitives, anything I could use to make it work ?
Edit: my link.xml file supposed to prevent code stripping
<linker>
<assembly fullname="Test.Serialization" preserve="all"/>
<assembly fullname="protobuf-net" preserve="all"/>
</linker>
SerializationTests.cs
using System.IO;
using ProtoBuf;
public static class SerializationTests
{
public static void AttemptSerializingData<T>(T dataToSerialize, bool displayProto, Entry entry)
{
if (displayProto)
entry.AddLog(Serializer.GetProto<T>());
entry.AddLog($"Serializing {dataToSerialize}");
var serializedObject = SerializeObject(dataToSerialize);
entry.AddLog($"Done ({serializedObject.Length})");
var deserializedObject = DeserializeObject<T>(serializedObject);
entry.AddLog($"Done! {deserializedObject}");
}
private static T DeserializeObject<T>(byte[] bytes)
{
T result;
using (var stream = new MemoryStream(bytes))
{
result = Serializer.Deserialize<T>(stream);
}
return result;
}
private static byte[] SerializeObject<T>(T objectToSave)
{
byte[] convertedObject;
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, objectToSave);
convertedObject = stream.ToArray();
}
return convertedObject;
}
}
The error is thrown when calling AttemptSerializingData from this GUI code:
using System.Collections.Generic;
using UnityEngine;
public class Entry : MonoBehaviour
{
private readonly List<string> logs = new List<string>();
private string a = "0", b = "5", c = "-2";
private bool displayProto;
private void OnGUI()
{
displayProto = GUILayout.Toggle(displayProto, "Display proto data");
a = GUILayout.TextField(a);
b = GUILayout.TextField(b);
c = GUILayout.TextField(c);
{
if (GUILayout.Button("Test int") && int.TryParse(a, out int _a) && int.TryParse(b, out int _b) && int.TryParse(c, out int _c))
{
logs.Clear();
var data = new DataDictionnary<string, int>();
data.AddValue("plop", _a);
data.AddValue("pop", _b);
data.AddValue("plp", _c);
SerializationTests.AttemptSerializingData(data, displayProto, this);
}
}
if (GUILayout.Button("Test string"))
{
logs.Clear();
var data = new DataDictionnary<string, string>();
data.AddValue("plop", a);
data.AddValue("pop", b);
data.AddValue("plp", c);
SerializationTests.AttemptSerializingData(data, displayProto, this);
}
{
if (GUILayout.Button("Test float") && float.TryParse(a, out float _a) && float.TryParse(b, out float _b) && float.TryParse(c, out float _c))
{
logs.Clear();
var data = new DataDictionnary<string, float>();
data.AddValue("plop", _a);
data.AddValue("pop", _b);
data.AddValue("plp", _c);
SerializationTests.AttemptSerializingData(data, displayProto, this);
}
if (GUILayout.Button("Test int int"))
{
logs.Clear();
var data = new DataDictionnary<int, int>();
data.AddValue(0, 5);
data.AddValue(4, 41);
data.AddValue(2, 8);
SerializationTests.AttemptSerializingData(data, displayProto, this);
}
if (GUILayout.Button("Test bool string"))
{
logs.Clear();
var data = new DataDictionnary<bool, string>();
data.AddValue(true, "ouyhgv");
data.AddValue(false, "hoipkip");
data.AddValue(false, "fguoii");
SerializationTests.AttemptSerializingData(data, displayProto, this);
}
}
foreach (var log in logs)
{
GUILayout.Label(log);
}
}
public void AddLog(string text)
{
logs.Add(text);
}
}
I found a solution here. Declaring all of my Dictionnaries with the attribute solves the problem.
[ProtoMap(DisableMap = true)]
[ProtoMember(1)]
private readonly Dictionary<string, string> _stringData;