【问题标题】:Strange .NET remoting SerializationException with MarshalByRefObject带有 MarshalByRefObject 的奇怪的 .NET 远程处理 SerializationException
【发布时间】:2010-08-23 14:24:25
【问题描述】:

我的应用程序出现远程处理问题。由于架构相当复杂,我将尝试用虚拟名称做一个简单的示例来说明问题。

考虑这些组件:

  • MyApp.Client.exe:客户端应用程序
  • MyApp.Service.exe:托管服务器的 Windows 服务
  • MyApp.Server.dll:服务器实现
  • MyApp.Shared.dll:包含通用接口和类型定义的共享库

在 MyApp.Shared.dll 中,我有这些接口:

public interface IFoo
{
    ...
}

public interface IFooManager
{
    IList<IFoo> GetFooList();
    ...
}

这两个接口都在 MyApp.Server.dll 中实现为MarshalByRefObjects

class Foo : MarshalByRefObject, IFoo
{
    ...
}

class FooManager : MarshalByRefObject, IFooManager
{
    public IList<IFoo> GetFooList()
    {
        IList<IFoo> foos = new List<IFoo>();
        // populate the list with instances of Foo
        // ...
        return foos;
    }

    ...
}

在客户端,我有一个代理实例指向服务器上的 FooManager 对象。当我在其上调用GetFooList 时,我可以看到FooManager.GetFooList() 方法已执行,但是当它返回时,我得到以下SerializationException

Unable to find assembly 'MyApp.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

Server stack trace: 
   at System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
   at System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
   at System.Runtime.Serialization.Formatters.Binary.ObjectMap.Create(String name, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryResponseMessage(Stream inputStream, IMethodCallMessage reqMsg, Boolean bStrictBinding)
   at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.DeserializeMessage(IMethodCallMessage mcm, ITransportHeaders headers, Stream stream)
   at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.SyncProcessMessage(IMessage msg)

Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at MyApp.Shared.IFooManager.GetFooList()
   ...
   at MyApp.Client.ViewModel.MainWindowViewModel.LoadFoos()
   ...

所以我猜它正在尝试序列化Foo 类(当GetFooList 返回一个空列表时我没有得到异常)或Foo 中使用的其他类型。但是为什么它会尝试序列化它呢?由于FooMarshalByRefObject,它不应该将代理返回到Foo 实例吗?无论如何,IFoo 接口不会暴露 MyApp.Server.dll 中定义的任何类型的对象...

问题之前没有出现,因为所有程序集都在同一个目录中,因此 MyApp.Server.dll 可能已加载到客户端 AppDomain 中(这不应该发生)。但是现在我正在尝试将客户端和服务器组件分开,因此客户端不应该依赖于服务器端程序集...

有人知道发生了什么吗?我怎样才能获得有关异常的更多详细信息(例如,它试图序列化哪种类型)?堆栈跟踪不是很有帮助...

【问题讨论】:

    标签: c# .net serialization remoting


    【解决方案1】:

    我做了一个非常简单的应用程序,你是对的,在远程处理 List 和 IFoo 都是编组的,不会发生序列化。

    首先我在 shared.dll 中创建了接口

    namespace Shared
    {
        public interface IFoo
        {
            string Name{get;set;}
        }
    
        public interface IFooMgr {
            IList<IFoo> GetList();
        }
    }
    

    然后我确实创建了一个 Foo 类、一个 Manager 并发布到远程处理:

    namespace Server
    {
        public class Foo : MarshalByRefObject, IFoo
        {
            public string Name
            {
                get;set;
            }
        }
    
        public class FooManager :  MarshalByRefObject, IFooMgr
        {
            public IList<IFoo> GetList()
            {
                IList<IFoo> fooList = new List<IFoo>();
                fooList.Add(new Foo { Name = "test" });
                fooList.Add(new Foo { Name = "test2" });
                return fooList;
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                ChannelServices.RegisterChannel(new TcpChannel(1237),true);
                System.Runtime.Remoting.RemotingServices.Marshal(new FooManager(),
                   "FooManager");
                Console.Read();
            }
        }
    }
    

    最后是客户端,作为另一个控制台应用程序,在 appdomain 之外并在另一个文件夹中,无法访问 server.exe:

    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                TcpChannel tcpChannel = new TcpChannel();
                ChannelServices.RegisterChannel(tcpChannel,true);
                Type requiredType = typeof(IFooMgr);
                IFooMgr remoteObject = (IFooMgr)Activator.GetObject(requiredType,
                    "tcp://localhost:1237/FooManager");
                IList<IFoo> foos = remoteObject.GetList();
                foreach (IFoo foo in foos)
                {
                     Console.WriteLine("IsProxy:{0}, Name:{1}",
                          RemotingServices.IsTransparentProxy(foo), foo.Name);
                }
                Console.ReadLine();
            }
        }
    }
    

    并且按您的预期工作,管理器和 foo 对象都被编组,没有序列化,所以问题可能在您的代码中更深层次。


    编辑: 如果您确定没有人创建可序列化的 IFoo 类,如下所示:

    [Serializable]
    public class Foo2 : IFoo
    {
        public string Name { get; set; }
    }
    

    然后,我唯一想到的是,可能有一个为您的类注册的 Surrogate 正在序列化它而不是使用默认的 MBR 行为。

    【讨论】:

    • 确实,我认为问题出在代码的更深处......更准确地说,它更深,因为我以某种方式解决了它,我只是不知道具体如何!我一定是改变了其他东西,让问题消失了
    • List&lt;&gt; 不是从MarshalByRefObject 派生但具有[Serializable] 属性时,为什么它会被编组?
    • List 不是作为 MBR 发送的,而是按值发送的,但元素是 MBR 发送的,因为它们继承自 MarsahByRefObject。 Remoting 序列化程序负责处理此问题,对象树中应通过引用发送的任何元素都以这种特殊方式处理。
    • 鉴于它神奇地消失了,shared.dll 的版本是否有可能在客户端和服务器之间不同步?
    【解决方案2】:

    如果您获得IFoos 的列表(Foo 是实现类),二进制序列化程序将尝试序列化所有Foo 对象。 MarshalByRefObject 支持代理生成,与序列化不同。通过网络发送对象需要对它们进行序列化。

    首先,Foo 必须标有[Serializable] 属性或实现ISerializable.,它的所有成员也必须这样做。

    您收到的错误表明服务器端找不到定义该类型的程序集。最简单的解决方法是强命名定义 Foo 的程序集并将其添加到服务器端的 GAC。

    【讨论】:

    • 但我不希望将 Foo 对象传输到客户端...我希望客户端通过代理远程操作它们
    • 如果您的客户端代码正在调用 GetFooList,它返回一个 IList,那么这些对象必须被序列化。您可以使用远程处理来远程执行方法,但是当对象从方法返回时,它们必须被序列化。
    • GetFooList 返回 IList,而不是 IList。客户端应该检索 Foo 对象的代理列表,而不是对象本身。正如我在我的问题中提到的,当程序集位于同一目录中时它工作正常,并且 Foo 类没有标记为 [Serializable],因此它无需序列化就可以工作......
    • 跨 AppDomain 移动对象引用时,不需要序列化。在跨进程边界或在计算机之间移动时,需要进行序列化。
    • 不,不是,这就是远程处理的全部意义......否则我的应用程序将永远无法运行
    【解决方案3】:

    这是 Remoting 的基石:您的 Foo 对象可以很高兴地成为 MarshalByRefObject 并由 Remoting 使用,但需要由 Remoting 调用。您创建了一个与FooManager 而不是Foo 通信的通道。 请记住,在远程会话中传入和传出的所有类型都必须是可序列化的。

    我会这样做: 有一个 GetAllFooIds(),它将一个 ID 列表/数组返回给 Foo,然后通过传递 Foo id 来使用 GetFoo。

    更新 我想也许我上面的陈述不够清楚。关键是,一个对象要么是可序列化的,要么是 MarshalByRefObject。在您的情况下, List 是可序列化的,因此不能保存 MarshalByRefObject 对象的实例。正如我所建议的那样,我将中断电话:一个用于获取 ID,另一个用于获取单个项目。慢,是的,但这是我能想到的唯一方法。

    【讨论】:

    • “在你的情况下, List 是可序列化的,因此不能保存 MarshalByRefObject 对象的实例”:这不是真的,请查看 jmservera 的答案。客户端收到的列表包含服务器上 Foo 对象的代理,而不是 Foo 的序列化实例
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-22
    • 2010-10-27
    • 1970-01-01
    相关资源
    最近更新 更多