Search Unity

C Sharp Binary Serialization

Discussion in 'Scripting' started by abarrett86, Oct 23, 2009.

  1. abarrett86

    abarrett86

    Joined:
    Sep 9, 2009
    Posts:
    6
    I am trying to use BinaryFormatter to serialize game data.

    I have serializable classes that I am able to serialize , write to a file, and deserialze without issue. However, when I serialize and write to the file, then stop the app and try to deserialize when running the next time, I get a file not found expception on the call BinaryFormatter::Deserialize with the file name a string of (seemingly) random characters.




    //Serialization Code
    fs = new FileStream(Application.dataPath + "/Game.xml", FileMode.Open);

    desPro = new DESCryptoServiceProvider();
    desPro.Key = ASCIIEncoding.ASCII.GetBytes("passpass");
    desPro.IV = ASCIIEncoding.ASCII.GetBytes("passpass");

    cs = new CryptoStream(fs, desPro.CreateEncryptor(), CryptoStreamMode.Write);

    bf.Serialize(cs, ss);

    cs.Close();
    fs.Close();





    //Deserialization Code
    fs = new FileStream(Application.dataPath + "/Game.xml", FileMode.Open);
    fs.Position = 0;

    desPro = new DESCryptoServiceProvider();
    desPro.Key = ASCIIEncoding.ASCII.GetBytes("passpass");
    desPro.IV = ASCIIEncoding.ASCII.GetBytes("passpass");

    cs = new CryptoStream(fs, desPro.CreateDecryptor(), CryptoStreamMode.Read);

    ss = (SavedState)bf.Deserialize(cs);

    fs.Close();
    cs.Close();



    Any help/insight is greatly appreciated
     
  2. jeremyace

    jeremyace

    Joined:
    Oct 12, 2005
    Posts:
    1,661
    The assembly name generated by Unity is generally random. This screws up the BinaryFormatter so when the assembly changes (I think it is every compile), the prev saved data will not load. The random characters is this assembly name.

    To fix this, you need to create and implement a DeserializationBinder to re-set the assembly and types.

    Here is an example binder:
    Code (csharp):
    1.  
    2. public sealed class VersionDeserializationBinder : SerializationBinder
    3. {
    4.     public override Type BindToType( string assemblyName, string typeName )
    5.     {
    6.         if ( !string.IsNullOrEmpty( assemblyName )  !string.IsNullOrEmpty( typeName ) )
    7.         {
    8.             Type typeToDeserialize = null;
    9.  
    10.             assemblyName = Assembly.GetExecutingAssembly().FullName;
    11.  
    12.             // The following line of code returns the type.
    13.             typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) );
    14.  
    15.             return typeToDeserialize;
    16.         }
    17.  
    18.         return null;
    19.     }
    20. }
    21.  
    You then set this in your BinaryFormater for deserialization:

    Code (csharp):
    1.  
    2.  formatter.Binder = new VersionDeserializationBinder();
    3.  
    A quick check in Google will provide more detail.

    -Jeremy
     
  3. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,603
    And in which way would the change of the assembly impact the Serialization of an assembly independent XML file? (always assuming its ensured that it is not part of the build so not within assets, because in that case all these attempts will fail)
     
  4. jeremyace

    jeremyace

    Joined:
    Oct 12, 2005
    Posts:
    1,661
    It's because he/she is using the BinaryFormatter to save the actual XML data, and the BinaryFormatter chokes if the assembly changes between serialize and deserialize.

    So that's why I gave the binder reply. Hopefully that is the actual problem.

    -Jeremy
     
  5. tgraupmann

    tgraupmann

    Joined:
    Sep 14, 2007
    Posts:
    828
    Thanks. This is very useful!
     
  6. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,603
    Wanted to thank too

    Revisited this thread recently as I'm on track to implement a "store / load scene state" capability for an iphone game and the Formatters are a must there.
    If I end using the xml one in the end will have to be seen, I would prefer the binary one.
     
  7. SoundGuy32

    SoundGuy32

    Joined:
    Oct 30, 2009
    Posts:
    17
    Hey,

    I'm trying to implement VersionDeserializationBinder but i'm geting this error:

    Assets/GameStateCS.cs(2604,25): error CS0246: The type or namespace name `Type' could not be found. Are you missing a using directive or an assembly reference?

    what am i doing wrong?

    Oded
     
  8. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,603
    you should be using System.Type unless you have using System; at the top (which is not that common in case of unity due to UnityEngine.Object vs System.Object
     
  9. DavidB

    DavidB

    Joined:
    Dec 13, 2009
    Posts:
    530
    Sorry to necro this thread again...

    But I'm currently stumped by the implementation of the SerializationBinder.

    I've got some code to save some data to a binary file, and reload that data... this all works well so long as I don't change any code to cause Unity to recompile. Once it does recompile, I can no longer load from my file, it says I cannot load... this sounds like what the OP originally had going on. The solution that was proposed is the SerializationBinder. I can only assume that it's my assembly that's changing as well, so I attempted to implement the code provided earlier in this thread.

    Sadly nothing changed.... so I decided to dig a bit deeper and see where it was failing... so I tossed a Debug.Log() into the SerializationBinder... only to see that it didn't get called. Does anyone know under what circumstances these Binders are called? I'm sure I've made a very simple mistake.. but I can't for the life of me see it.

    Serialization Code:
    Code (csharp):
    1. Stream stream = File.Open(path, FileMode.Create);
    2.         BinaryFormatter bFormatter = new BinaryFormatter();
    3.         bFormatter.Serialize(stream, data);
    4.         stream.Close();
    Deserialization Code:
    Code (csharp):
    1. object result;
    2.         Stream stream = File.Open(path, FileMode.Open);
    3.         BinaryFormatter bFormatter = new BinaryFormatter();
    4.         bFormatter.Binder = new VersionDeserializationBinder();
    5.         result = bFormatter.Deserialize(stream);
    6.         stream.Close();
    7.         return result;
    SerializationBinder code:
    Code (csharp):
    1. public sealed class VersionDeserializationBinder : SerializationBinder
    2.     {
    3.         public VersionDeserializationBinder() : base() { }
    4.  
    5.         public override Type BindToType(string assemblyName, string typeName)
    6.         {
    7.             if (!string.IsNullOrEmpty(assemblyName)  !string.IsNullOrEmpty(typeName))
    8.             {
    9.                 Type typeToDeserialize = null;
    10.  
    11.                 assemblyName = Assembly.GetExecutingAssembly().FullName;
    12.  
    13.                 // The following line of code returns the type.
    14.                 typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
    15.  
    16.                 return typeToDeserialize;
    17.             }
    18.  
    19.             return null;
    20.         }
    21.     }
    Has anyone run across this before? Why might my Binder not be called?

    Thanks for any/all help.
    Cheers!
     
  10. DavidB

    DavidB

    Joined:
    Dec 13, 2009
    Posts:
    530
    Update:

    I have managed to find a slightly different method to overcome the downfall of randomized Binary names every code change. Instead of adding List<type>'s to the sInfo directly... instead you can serialize the count first, and then each element individually. It has something to do with Generic Lists being Strong-Typed....which leads to issues. Seems to be working well, but if anyone knows why my VersionBinder wasn't calling... I'd still like to know.

    Cheers
     
  11. gilson

    gilson

    Joined:
    Sep 13, 2010
    Posts:
    36
    Sorry to up this thread, but i'm having the same problem.

    I'm doing a binary serialization of a dictionary, and everything goes well aslong tht you dont chenge the project, if i change any code, it recompiles and i get an error.

    Anything i can do to solve this?

    DavidB, did you have to save the list in separate parts and the put it together on loading it? How you did that?

    Thank you!
     
  12. jmunozar

    jmunozar

    Joined:
    Jun 23, 2008
    Posts:
    1,068
    Hey Gilson, just move the dictionary to 2 Arrays when serializing and then reconstruct the Dictionary when deserializing with your serialized arrays.

    I was having the same problem with a List<type>, and this just worked out for me! :D

    checkout:

    Code (csharp):
    1.  
    2.  
    3.     private List<Entity> Entities;
    4.  
    5.     // Deserialization
    6.     public InteractiveSpace(SerializationInfo info, StreamingContext ctxt)
    7.     {
    8.         // We save the Entities list in an Array so the VersionDeserializationBinder works as expected if we change the code.
    9.         // Seems that Unity and Mono are not so friendly when serializing generic attributes.
    10.         Entity[] loadedEntitiesArr = (Entity[]) info.GetValue("EntitiesList", typeof(Entity[]));
    11.         Entities = new List<Entity>(loadedEntitiesArr);
    12.        
    13.  
    14.     }
    15.     // Serialization function
    16.     public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
    17.     {
    18.         Entity[] savedArrEntities = Entities.ToArray();
    19.         info.AddValue("EntitiesList", savedArrEntities);
    20.        
    21.     }
    22.  
    and make sure to use the deserialization binder.