Класс binaryformatter.Десериализовать "не удалось найти сборку" после ILMerge

У меня есть решение c# со ссылочной dll (также C# с той же версией .Net). Когда я создаю решение и запускаю полученный exe, без слияния exe и указанной dll, все работает нормально.

теперь я хочу объединить их в один exe. Я запускаю ILMerge, и все работает нормально. Я пытаюсь выполнить exe, и он, кажется, работает нормально, пока он не попытается десериализовать объект, определенный в указанной dll.

using (Stream fstream = new FileStream(file_path, FileMode.Open))
{
    BinaryFormatter bf = new BinaryFormatter();
    return bf.Deserialize(fstream) as ControlledRuleCollection; 
    // throws unable to find assembly exception
}

возможно ли какой-то вариант ILMerge, который я здесь упускаю?

9 ответов


похоже, что вы сериализовали объект внутри DLL, затем объединили все сборки с ILMerge и теперь пытаетесь десериализовать этот объект. Это просто не сработает. Процесс десериализации для двоичной сериализации попытается загрузить тип объекта из исходной библиотеки DLL. Эта DLL не существует post ILMerge и, следовательно, десериализация завершится неудачей.

процесс сериализации и десериализации должен работать как до, так и после слияния. Этого не может быть. смешанный


Это можно сделать, создав и добавив подкласс SerializationBinder, который изменит имя сборки до десериализации.

sealed class PreMergeToMergedDeserializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = null;

        // For each assemblyName/typeName that you want to deserialize to
        // a different type, set typeToDeserialize to the desired type.
        String exeAssembly = Assembly.GetExecutingAssembly().FullName;


        // The following line of code returns the type.
        typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
            typeName, exeAssembly));

        return typeToDeserialize;
    }
}

затем, когда deserializating добавить это в класс binaryformatter:

BinaryFormatter bf = new BinaryFormatter();
bf.Binder = new PreMergeToMergedDeserializationBinder();
object obj = bf.Deserialize(ms);

возможно, вы сериализовали это из отдельной сборки, а затем попытались десериализовать его с другой сборкой (или более новой версией той же сборки).

обсуждение здесь


SerializationBinder также был моим решением. Но у меня есть класс в DLL, которые ссылается. Поэтому я должен искать во всех сборках загрузки. Я изменил ответы bevor с параметром, если связующее должно искать в DLL.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ibKastl.Helper
{
   public static class BinaryFormatterHelper
   {
      public static T Read<T>(string filename, Assembly currentAssembly)
      {
         T retunValue;
         FileStream fileStream = new FileStream(filename, FileMode.Open);

         try
         {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Binder = new SearchAssembliesBinder(currentAssembly,true);            
            retunValue = (T)binaryFormatter.Deserialize(fileStream);
         }
         finally
         {
            fileStream.Close();
         }

         return retunValue;
      }

      public static void Write<T>(T obj, string filename)
      {
         FileStream fileStream = new FileStream(filename, FileMode.Create);
         BinaryFormatter formatter = new BinaryFormatter();
         try
         {
            formatter.Serialize(fileStream, obj);
         }
         finally
         {
            fileStream.Close();
         }
      }
   }

   sealed class SearchAssembliesBinder : SerializationBinder
   {
      private readonly bool _searchInDlls;
      private readonly Assembly _currentAssembly;

      public SearchAssembliesBinder(Assembly currentAssembly, bool searchInDlls)
      {
         _currentAssembly = currentAssembly;
         _searchInDlls = searchInDlls;
      }

      public override Type BindToType(string assemblyName, string typeName)
      {
         List<AssemblyName> assemblyNames = new List<AssemblyName>();
         assemblyNames.Add(_currentAssembly.GetName()); // EXE

         if (_searchInDlls)
         {
            assemblyNames.AddRange(_currentAssembly.GetReferencedAssemblies()); // DLLs
         }

         foreach (AssemblyName an in assemblyNames)
         {
            var typeToDeserialize = GetTypeToDeserialize(typeName, an);
            if (typeToDeserialize != null)
            {
               return typeToDeserialize; // found
            }
         }

         return null; // not found
      }

      private static Type GetTypeToDeserialize(string typeName, AssemblyName an)
      {
         string fullTypeName = string.Format("{0}, {1}", typeName, an.FullName);
         var typeToDeserialize = Type.GetType(fullTypeName);
         return typeToDeserialize;
      }
   }

}

использование:

const string FILENAME = @"MyObject.dat";

// Serialize
BinaryFormatterHelper.Write(myObject1,FILENAME);

// Deserialize
MyObject myObject2 = BinaryFormatterHelper.Read<MyObject>(FILENAME, Assembly.GetExecutingAssembly()); // Current Assembly where the dll is referenced

Если вы объединяете сборки в существующую (например, все библиотеки DLL в EXE), вы можете использовать решение, предложенное в ответ:

// AssemblyInfo.cs for My.exe
[assembly: TypeForwardedTo(typeof(SomeTypeFromMergedDLL))]

Это по крайней мере работает для десериализации предварительно слить. IL-Merge также все еще проходит; даже если вы не можете скомпилировать с типом-вперед к типу той же сборки...

Я еще не пробовал, если сериализация работает после слияния. Но я буду держать свой ответ в курсе.


У меня была ситуация, когда сериализованные данные хранились в SQL server старой службой .NET, которая была на месте в течение многих лет. Мне нужно было получить данные из SQL и столкнулся с этим. Я смог обратиться к нему .exe и заставить его работать, пока я не использовал упомянутое выше решение. Однако мои имена были другими.

sealed class Version1ToVersion2DeserializationBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            Type typeToDeserialize = null;

            // For each assemblyName/typeName that you want to deserialize to a different type, set typeToDeserialize to the desired type.
            String assemVer1 = assemblyName;
            String typeVer1 = typeName;

            if (assemblyName == assemVer1 && typeName == typeVer1)
            {
                // To use a type from a different assembly version, change the version number.
                assemblyName = Assembly.GetExecutingAssembly().FullName;
                // To use a different type from the same assembly, change the type name.
                typeName = "projectname.typename";
            }

            // The following line of code returns the type.
            typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
            return typeToDeserialize;
        }
    }

Я получил решение

   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;
}

}

проблема десериализации: ошибка при десериализации из другой версии программы


    public sealed class DeserializationBinder : SerializationBinder
{
    private readonly string _typeName;
    private readonly Assembly _assembly;
    public DeserializationBinder(Assembly assembly, string typeName)
    {
        _typeName = typeName;
        _assembly = assembly;
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = null;
        if (!assemblyName.Contains("System") && !assemblyName.Contains("mscorlib"))
        {
            String currentAssembly = _assembly.FullName;
            assemblyName = currentAssembly;
            typeName = _typeName;
        }
        typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
            typeName, assemblyName));
        return typeToDeserialize;
    }
}

для тех, кто имеет эту проблему, пытаясь десериализоваться из другой сборки, я нашел это решение, которое кажется отличным для меня, используя небольшой класс "BindChanger" с общим пространством имен для рассматриваемого типа объекта. https://www.daniweb.com/programming/software-development/threads/339638/deserializing-in-a-different-assembly