0

I finally decided myself to post my problem, after a couple of hours spent searching the Internet for solutions and trying some.

[Problem Context]

I am developing an application which will be deployed in two parts:

  • an XML Importer tool: its role is to Load/Read an xml file in order to fill some datastructures, which are afterwards serialized into a binary file.
  • the end user application: it will Load the binary file generated by the XML Importer and do some stuff with the recovered data structures.

For now, I only use the XML Importer for both purposes (meaning I first load the xml and save it to a binary file, then I reopen the XML Importer and load my binary file).

[Actual Problem]

This works just fine and I am able to recover all the data I had after XML loading, as long as I do that with the same build of my XML Importer. This is not viable, as I will need at the very least two different builds, one for the XML Importer and one for the end user application. Please note that the two versions of the XML Importer I use for my testing are exactly the same concerning the source code and thus the datastructures, the only difference lies in the build number (to force a different build I just add a space somewhere and build again).

So what I'm trying to do is:

  • Build a version of my XML Importer
  • Open the XML Importer, load an XML file and save the resulting datastructures to a binary file
  • Rebuild the XML Importer
  • Open the XML Importer newly built, load the previously created binary file and recover my datastructures.

At this time, I get an Exception:

SerializationException: Could not find type 'System.Collections.Generic.List`1[[Grid, 74b7fa2fcc11e47f8bc966e9110610a6, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadType (System.IO.BinaryReader reader, TypeTag code)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadTypeMetadata (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectInstance (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObject (BinaryElement element, System.IO.BinaryReader reader, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info)

For your information (don't know if useful or not), the actual type it is struggling to deserialize is a List, Grid being a custom Class (which is correctly serializable, as I am able to do it when using the same version of XML Importer).

[Potential Solution]

I do believe it comes from somewhere around the Assembly, as I read many posts and articles about this. However, I already have a custom Binder taking care of the differences of Assembly names, looking like this:

public sealed class VersionDeserializationBinder : SerializationBinder
{ 
    public override Type BindToType( string assemblyName, string typeName )
    { 
        if ( !string.IsNullOrEmpty( assemblyName ) && !string.IsNullOrEmpty( typeName ) )
        { 
            Type typeToDeserialize = null; 
            assemblyName = Assembly.GetExecutingAssembly().FullName; 
            // The following line of code returns the type. 
            typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) ); 

            return typeToDeserialize; 
        } 

        return null; 
    }
}

which I assign to the BinaryFormatter before deserializing here:

    public static SaveData Load (string filePath) 
    {
        SaveData data = null;//new SaveData ();
        Stream stream;

        stream = File.Open(filePath, FileMode.Open);


        BinaryFormatter bformatter = new BinaryFormatter();
        bformatter.Binder = new VersionDeserializationBinder(); 
        data = (SaveData)bformatter.Deserialize(stream);
        stream.Close();

        Debug.Log("Binary version loaded from " + filePath);

        return data; 
    }

Do any of you guys have an idea on how I could fix it? Would be awesome, pretty please :)

ohmantics
  • 1,799
  • 14
  • 16
Thomas
  • 3
  • 1
  • 2
  • Are you using strongly-named assemblies? That may be why it can't recognize the classes as being the same. – antlersoft Jun 16 '11 at 15:39
  • To be honest, I don't know the first thing about assemblies, today was the first time I ever had to get around these. I've seen that there are many msdn pages about these, though I didn't have the time to read through them as I stumbed upon them a bit late in the afternoon. Thus to answer your question, I didn't modify anything concerning assemblies, didn't set up anything special; I don't know if the assemblies are strongly-named by default (in which case I am using these) or not (in that case I shouldn't be using strongly-named assemblies). – Thomas Jun 16 '11 at 17:21

3 Answers3

1

I just bumped into your thread while I had the same problem. Especially your code sample with the SerializationBinder helped me a lot. I just had to modify it slightly to tell a difference between my own assemblies and those of Microsoft. Hopefully it still helps you, too:

sealed class VersionDeserializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = null;
        string currentAssemblyInfo = Assembly.GetExecutingAssembly().FullName;

        //my modification
        string currentAssemblyName = currentAssemblyInfo.Split(',')[0];
        if (assemblyName.StartsWith(currentAssemblyName))assemblyName = currentAssemblyInfo;

        typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName));
        return typeToDeserialize;
    }
}
1

Move the working bits to a separate assembly and use the assembly in both "server" and "client". Based on your explanation of the problem, this should get around the "wrong version" problem, if that is the core issue. I would also take any "models" (i.e. bits of state like Grid) to a domain model project, and use that in both places.

Gregory A Beamer
  • 16,870
  • 3
  • 25
  • 32
  • "Move the working bits to a separate assembly and use the assembly in both "server" and "client"" => How can I achieve this? As I pointed out in the reply comment to antlersoft above, I have at this point no idea AT ALL on how to deal with assemblies, and only a very vague idea on what it is used for. I will most certainly read about that tomorrow when I'm back at work, but if you have a starting point it would be terrific. – Thomas Jun 16 '11 at 17:32
  • So, after antlersoft explanation below, I realised that this has to be the solution indeed. Unfortunately, it doesn't do the job for me as I'm working with Unity, and it makes it way harder to use libraries, especially as the target application will be cross platforms. I thank you both for your help and will work on another way to do what I want without using the tool I wanted to :/ – Thomas Jun 17 '11 at 09:50
0

I believe the problem is that you are telling it to look for List<> in the executing assembly, whereas in fact it is in the System assembly. You should only re-assign the assembly name in your binder if the original assembly is one of yours.

Also, you might have to handle the parameter types for generics specifically in the binder, by parsing out the type name and making sure the parameter types are not specific to the foreign assembly when you return the parameterized generic type.

antlersoft
  • 14,636
  • 4
  • 35
  • 55
  • Concerning the first part, I get why it can be unstable, though I don't think this is the case here as I also tried to comment out the re-assigning bit and still got exactly the same output. Now about the second part... I don't think I am getting half of it :/ I am terribly sorry about that, though I only started working with serialization yesterday and am still a pure N00B in that domain... (between this and the assemblies, it must look like I "don't want" to understand your answers >_< though I assure you they are most welcome!) – Thomas Jun 16 '11 at 17:40
  • An assembly is a .Net executable, either an .exe or .dll. Each class belongs to an assembly. If a class is in different assemblies, .Net will tend to think they are different classes, even if the source is the same. The solution above suggests putting your common classes (the ones you serialize) into a .dll assembly (this would be a separate project within Visual Studio) and have both of your .exe's reference that assembly. Then there won't be any confusion about class identity when deserializing (you won't need your binder at all). It makes deployment slightly more complex, you need both – antlersoft Jun 16 '11 at 17:48
  • Thank you for that clarification. Very concise and clear. Now the hurting part: I am not working on Visual Studio, which would have made it simpler as you suggest, but on Unity 3 (game engine + middleware, in case you don't know it). Now I'm not sure about how it handles C# assemblies, nor how it is to create dependecies within Unity, eventhough I'm pretty sure it is possible. The only bit I am REALLY afraid of is that my application will be cross-platform (which is the interest of using Unity) for OSX, iOS and Android, which may be problematic depending on how the dependencies are handled.. – Thomas Jun 16 '11 at 18:27