views:

228

answers:

3

I'm having a remoting issue in my application. Since the architecture is quite complex, I'll try to make an simple example with dummy names to illustrate the problem.

Consider these components:

  • MyApp.Client.exe: client application
  • MyApp.Service.exe: Windows service that hosts the server
  • MyApp.Server.dll: server implementation
  • MyApp.Shared.dll: shared library containing common interface and type definitions

In MyApp.Shared.dll, I have these interfaces:

public interface IFoo
{
    ...
}

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

Both interfaces are implemented in MyApp.Server.dll as 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;
    }

    ...
}

On the client side, I have a proxy instance to the FooManager object on the server. When I call GetFooList on it, I can see that the FooManager.GetFooList() method is executed, but when it returns I get the following 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()
   ...

So I'm guessing it is trying to serialize the Foo class (I don't get an exception when GetFooList returns an empty list) or another type used in Foo. But why would it try to serialize it ? Since Foo is a MarshalByRefObject, shouldn't it return a proxy to the Foo instance ? And anyway, the IFoo interface doesn't expose any object of types defined in MyApp.Server.dll...

The problem didn't appear before because all assemblies were in the same directory, so MyApp.Server.dll was probably loaded in the client AppDomain (which isn't supposed to happen). But now I'm trying to separate the client and server components, so the client shouldn't depend on a server-side assembly...

Does anyone have any idea about what is going on ? And how could I get more details about the exception (e.g. which type is it trying to serialize) ? The stack trace is not very helpful...

A: 

If you're getting a list of IFoos (Foo being the implementing class,) the binary serializer will attempt to serialize all the Foo objects. MarshalByRefObject supports proxy generation, which is different from serialization. Sending objects over the wire will require them to be serialized.

First and foremost, Foo must be marked with the [Serializable] attribute or implement ISerializable. All its members must also.

The error you're getting suggests that the server end can't find the assembly that defines the type. The easiest fix is to strongly-name the assembly that defines Foo and add it to the GAC on the server side.

Dave Swersky
But I don't want the Foo objects to be transmitted to the client... I want the client to manipulate them remotely, via a proxy
Thomas Levesque
If your client code is calling GetFooList, which returns an IList<Foo>, then those objects must be serialized. You can use remoting to *execute methods* remotely, but when objects are returned from a method they must be serialized.
Dave Swersky
GetFooList returns IList<IFoo>, not IList<Foo>. The client should retrieve a list of proxy to Foo objects, not the objects themselves. As I mentioned in my question, it works fine when the assemblies are in the same directory, and the Foo class is not marked [Serializable], so it works without serializing it...
Thomas Levesque
When you're moving the object reference across AppDomains, no serialization is necessary. When moving across process boundaries or between computers, serialization is required.
Dave Swersky
No it's not, that's the whole point of remoting... otherwise my app would never have worked
Thomas Levesque
Does it work if you mark Foo as serializable?
whatknott
Even if you mark it as serializable the Remoting's SerializationSurrogate will always create proxies (ObjRef) of the MarshalByRef objects. No real serialization will happen.
jmservera
A: 

This is the cornerstone of the Remoting: your Foo object can happily be MarshalByRefObject and used by Remoting but needs to be called by Remoting. You have created a channel to communicate with the FooManager and not the Foo. Remember all types passed to and from in a remoting session must be serializable.

Here is how I would do it: Have a GetAllFooIds() which return a list/array of IDs to Foo and then use GetFoo by passing the Foo id.

Update I think perhaps my statement above was not clear enough. The point is, an object is either serialisable or MarshalByRefObject. In your case, List<> is serialisable and as such cannot hold instance of MarshalByRefObject object. As I suggested, I will break the calls: one to get ids and another to get to the individual item. Slow, yes, but it is the only way I can think of to do it.

Aliostad
"In your case, List<> is serialisable and as such cannot hold instance of MarshalByRefObject object": this is not true, check out jmservera's answer. The list received by the client contains proxies to Foo objects on the server, not serialized instances of Foo
Thomas Levesque
A: 

I did very a simple application and you are right, in remoting both the List and the IFoo are marshaled, no serialization occurs.

First I created the interfaces in the shared.dll

namespace Shared
{
    public interface IFoo
    {
        string Name{get;set;}
    }

    public interface IFooMgr {
        IList<IFoo> GetList();
    }
}

Then I did create a Foo class, a Manager and published to remoting:

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

And finally the client, as another console application, out of the appdomain and in another folder without access to the 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();
        }
    }
}

And worked as you expected, both the manager and the foo objects were marshaled, nothing serialized, so the problem may be deeper in your code.


Edit: If you are sure that nobody has created a serializable IFoo class, like this:

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

Then, the only thing that comes to my mind is that there might be a Surrogate registered for your class that was serializing it instead of using the default MBR behavior.

jmservera
Indeed, I think the problem is somewhere deeper in the code... more precisely it *was* deeper, because I somehow solved it, I just don't know exactly how ! I must have changed something else that made the problem go away
Thomas Levesque
Why does the `List<>` get marshalled when it doesn’t derive from `MarshalByRefObject` but has a `[Serializable]` attribute?
Timwi
The List is not being sent as MBR but by value, but the elements are sent MBR because they inherit from MarsahByRefObject. The Remoting serializer takes care of this, any element in the object tree that should be sent by reference is treated in that special way.
jmservera