【问题标题】:BinaryFormatter.Deserialize "unable to find assembly" after ILMerge在 ILMerge 之后 BinaryFormatter.Deserialize “无法找到程序集”
【发布时间】:2011-07-07 10:05:06
【问题描述】:

我有一个带有引用 dll 的 C# 解决方案(也是具有相同 .Net 版本的 C#)。当我构建解决方案并运行生成的 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 选项吗?

【问题讨论】:

  • 对象是从合并前的程序集还是合并后的程序集中序列化的?
  • 我不想这么说,但我总是试图阻止人们使用 BinaryFormatter 的另一个原因。这种与类型元数据过于紧密的耦合会导致巨大的痛苦。如果您有兴趣在未来避免这种情况(以及许多其他痛苦情况),我可以推荐更好的选择。
  • 你知道二进制序列化并不是一种存储格式,对吧?它只是一种临时传输格式,当通过二进制序列化程序完成时,对象的长期存储有太多限制。升级您的程序,告别旧文件。
  • 点了。我根本没有考虑长期存储,但对于我正在编写的简单实用程序来说,它看起来非常简单。哦,好吧。
  • @Lasse 说得好;但我可以强调一下问题是 BinaryFormatter,而不是一般的“二进制序列化”。当然,很可能(咳)编写一个恰好产生二进制输出的行为良好的序列化程序。

标签: c# ilmerge binary-serialization


【解决方案1】:

您可以通过创建和添加一个 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;
    }
}

然后在反序列化时将其添加到 BinaryFormatter:

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

【讨论】:

  • 优秀的答案!这为我节省了很多时间!谢谢!
【解决方案2】:

听起来您已经在 DLL 中序列化了一个对象,然后将所有程序集与 ILMerge 合并,现在正试图反序列化该对象。这根本行不通。二进制序列化的反序列化过程将尝试从原始 DLL 加载对象的类型。此 DLL 在 ILMerge 后不存在,因此反序列化将失败。

序列化和反序列化过程都需要在合并前或合并后操作。不能混用

【讨论】:

    【解决方案3】:

    SerializationBinder 也是我的解决方案。但是我在引用的 DLL 中有这个类。所以我必须在所有加载程序集中搜索。如果活页夹应该在 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
    

    【讨论】:

      【解决方案4】:

      您可能已经从单独的程序集中对其进行了序列化,然后尝试使用另一个程序集(或同一程序集的较新版本)对其进行反序列化。

      一些讨论here

      【讨论】:

        【解决方案5】:

        对于任何遇到此问题并尝试从不同程序集反序列化的人,我发现这个解决方案似乎对我来说非常有用,它使用了一个小型“BindChanger”类,该类具有用于相关对象类型的共享命名空间。 https://www.daniweb.com/programming/software-development/threads/339638/deserializing-in-a-different-assembly

        【讨论】:

        • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
        【解决方案6】:

        我为这个问题找到了另一种解决方案。我的问题可能有点不同......

        我试图从序列化的同一个库中反序列化,但它找不到正确的程序集来执行此操作。我尝试了上述所有解决方案,但都没有奏效。

        我在另一个网站上找到了解决方案 (Here)

        简单来说就是重写+tor中AppDomain.CurrentDomain.AssemblyResolve的ResolveEventHandler

        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
        

        然后实现“CurrentDomain_AssemblyResolve”的方法如下:

        System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
          System.Reflection.Assembly ayResult = null;
          string sShortAssemblyName = args.Name.Split(',')[0];
          System.Reflection.Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
          foreach (System.Reflection.Assembly ayAssembly in ayAssemblies)
          {
              if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
              {
                  ayResult = ayAssembly;
                  break;
              }
          }
          return ayResult;
        }
        

        这为我修复了允许我反序列化的错误。

        【讨论】:

          【解决方案7】:

          如果您将程序集合并到现有程序集(例如所有 DLL 到 EXE),您可以使用this answer 中提出的解决方案:

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

          这至少适用于反序列化预合并。 IL-Merge 也仍然通过;即使您不能使用类型转发编译到同一程序集的类型...

          我还没有尝试过,如果序列化在合并后仍然有效。但我会不断更新我的答案。

          【讨论】:

            【解决方案8】:

            我遇到过一种情况,其中序列化的数据由一个已经存在多年的旧 .NET 服务存储在 SQL Server 中。我需要从 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;
                    }
                }
            

            【讨论】:

              【解决方案9】:

              我找到了解决办法

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

              }

              Deserialization problem: Error when deserializing from a different program version

              【讨论】:

                【解决方案10】:
                    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;
                    }
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-07-31
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-01-10
                  相关资源
                  最近更新 更多