Strange .NET使用MarshalByRefObject远程处理SerializationException

时间:2010-08-23 14:24:26

标签: c# .net serialization remoting

我的申请中有一个远程问题。由于架构非常复杂,我将尝试使用虚拟名称来举例说明问题。

考虑以下组件:

  • 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中(不应该发生)。但现在我正在尝试分离客户端和服务器组件,因此客户端不应该依赖于服务器端组件...

有没有人知道发生了什么?我怎样才能获得有关异常的更多细节(例如,它尝试序列化的类型)?堆栈跟踪不是很有用......

3 个答案:

答案 0 :(得分: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行为。

答案 1 :(得分:0)

如果您获得IFoosFoo作为实现类)的列表,则二进制序列化程序将尝试序列化所有Foo个对象。 MarshalByRefObject支持代理生成,这与序列化不同。通过线路发送对象将需要序列化它们。

首先,Foo必须使用[Serializable]属性标记或实施ISerializable.其所有成员也必须。{/ p>

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

答案 2 :(得分:0)

这是Remoting的基石:你的Foo对象可以很乐意MarshalByRefObject并被Remoting使用,但需要通过Remoting调用。您已创建了与FooManager而非Foo进行通信的渠道。 请记住,在远程会话中传入和传出的所有类型都必须是可序列化的。

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

<强>更新 我想也许我上面的陈述不够明确。关键是,对象是可序列化的或MarshalByRefObject。在您的情况下,List&lt;&gt;是可序列化的,因此不能保存MarshalByRefObject对象的实例。正如我所建议的那样,我将打破电话:一个获取ID,另一个获取单个项目。慢,是的,但这是我能想到的唯一方法。