1

I moved the code of my c++/cli (visual studio 2010) project into a namespace. Before that, everything was in the global namespace, but this has several drawbacks which needed to be addressed.

After the renaming, the binary deserialization of existing save files fail, with the following error message:

Serialization Exception occurred.
The object with ID 11 was referenced in a fixup but does not exist.

To address the renaming of the classes - which now reside in a namespace - I use a SerializationBinder.

ref class MySerializationBinder sealed: SerializationBinder
{
public:
    virtual Type^ BindToType (String^ assemblyname, String^ typeName) override
    {
        Type^ result = Type::GetType (typeName);
        if (result == nullptr)
        {
            result = Type::GetType ("MyNamespace." + typeName);
        }
        return result;
    }
};

Does anyone have a clue of what might be going wrong here?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
chtenb
  • 14,924
  • 14
  • 78
  • 116
  • Can you share an example of a type that does not work? Is it generic? Are there any generics involved, such as `List`? To deal with generic names, see e.g. [How to create a SerializationBinder for the Binary Formatter that handles the moving of types from one assembly and namespace to another](http://stackoverflow.com/questions/19666511), [Format of generic type namein SerializationBinder](http://question.ikende.com/question/3937343437383936) or [SerializationBinder and parsing type fullname](https://social.msdn.microsoft.com/Forums/en-US/8b1349c7-4e98-46bd-9419-da5cd8d2e197/) – dbc Oct 23 '15 at 16:52
  • Also, can you share the code that calls `BinaryFormatter`? And is your `SerializationBinder` in the same assembly as the type being moved? – dbc Oct 23 '15 at 16:52
  • @dbc Indeed generics wrapping my types are involved. So I sense that the BindToType is not called recursively on these? – chtenb Oct 26 '15 at 09:03
  • You can verify in the debugger (or just by adding a `Console.WriteLine()`) but I believe not. You'll need to parse it yourself; see the links above. – dbc Oct 26 '15 at 09:05
  • Indeed, it appears not. Thank you for your suggestion. – chtenb Oct 26 '15 at 09:42

1 Answers1

1

Translating the code from How to create a SerializationBinder for the Binary Formatter that handles the moving of types from one assembly and namespace to another into very quick n dirty c++ cli, we get something like

virtual Type^ BindToType (String^ assemblyName, String^ typeName) override
{
    auto m = Regex::Match (typeName, "^(?<gen>[^\\[]+)\\[\\[(?<type>[^\\]]*)\\](,\\[(?<type>[^\\]]*)\\])*\\]$");
    if (m->Success)
    {
        // generic type
        Type^ gen = GetFlatTypeMapping (assemblyName, m->Groups["gen"]->Value);
        List<Type^>^ genArgs = gcnew List<Type^> ();
        for each(Capture^ c in Enumerable::Cast<Capture^> (m->Groups["type"]->Captures)){
            Match^ m2 = Regex::Match (c->Value, "^(?<tname>.*)(?<aname>(,[^,]+){4})$");
            String^ tname = m2->Groups["tname"]->Value->Trim ();
            Type^ t = BindToType ("", tname);
            genArgs->Add(t);
        }
        return gen->MakeGenericType (genArgs->ToArray ());
    }
    return GetFlatTypeMapping (assemblyName, typeName);
}

Type^ GetFlatTypeMapping (String^ assemblyName, String^ typeName)
{
    Type^ result = Type::GetType (typeName);
    if (result == nullptr)
    {
        result = Type::GetType ("MyNamespace." + typeName);
    }
    if (result == nullptr)
    {
        if (typeName->Contains ("BindingList"))
            result = BindingList<int>::typeid->GetGenericTypeDefinition (); //int is just a placeholder
        if (typeName->Contains ("KeyValuePair"))
            result = KeyValuePair<int, int>::typeid->GetGenericTypeDefinition (); //int is just a placeholder
    }
    if (result == nullptr)
        throw gcnew Exception ("Fail");
    return result;
}

However, since I know which classes to translate, I found the following way easier. This way we do not have to care about nested generics either. The variable OldClassList is assumed to be a list of strings containing the classnames that have to be moved into the namespace MyNamespace.

virtual Type^ BindToType (String^ assemblyName, String^ typeName) override
{
    String^ newTypeName = typeName;
    for each(String^ name in OldClassList)
        newTypeName = Regex::Replace (newTypeName, "(?<![.\\w])" + name + "\\b", "MyNamespace." + name);
    newTypeName = Regex::Replace (newTypeName, "\\OldAssembly, Version", "NewAssembly, Version");

    Type^ result = Type::GetType (newTypeName);
    if (result == nullptr)
        throw gcnew Exception ("Could not parse the string {0} to a valid type."->Format(typeName));

    return result;
}
Community
  • 1
  • 1
chtenb
  • 14,924
  • 14
  • 78
  • 116