Странное исключение .NET с удаленным взаимодействием с MarshalByRefObject - PullRequest
3 голосов
/ 23 августа 2010

У меня возникла проблема удаленного взаимодействия в моем приложении. Поскольку архитектура довольно сложная, я попытаюсь сделать простой пример с фиктивными именами, чтобы проиллюстрировать проблему.

Рассмотрим эти компоненты:

  • 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. Но почему это попыталось бы сериализовать это? Поскольку Foo является MarshalByRefObject, разве он не должен возвращать прокси для экземпляра Foo? И вообще, интерфейс IFoo не предоставляет никаких объектов типов, определенных в MyApp.Server.dll ...

Проблема не возникала раньше, поскольку все сборки находились в одном каталоге, поэтому MyApp.Server.dll, вероятно, был загружен в клиентский домен приложений (чего не должно быть). Но сейчас я пытаюсь разделить клиентский и серверный компоненты, чтобы клиент не зависел от сборки на стороне сервера ...

Кто-нибудь имеет представление о том, что происходит? И как я могу получить более подробную информацию об исключении (например, какой тип он пытается сериализовать)? Трассировка стека не очень полезна ...

Ответы [ 3 ]

1 голос
/ 18 сентября 2010

Я сделал очень простое приложение, и вы правы, в удаленном взаимодействии и List, и IFoo маршалируются, сериализация не происходит.

Сначала я создал интерфейсы в shared.dll

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

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

Затем я создал класс Foo, менеджер и опубликовал для удаленного взаимодействия:

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

И, наконец, клиент, как другое консольное приложение, выходит из домена приложения и в другую папку без доступа к серверу.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 были маршалированы, ничего не сериализовано, поэтому проблема может быть глубже в вашем коде.


Edit: Если вы уверены, что никто не создал сериализуемый класс IFoo, например:

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

Тогда единственное, что мне приходит в голову, - это то, что Суррогат зарегистрирован для вашего класса, который сериализует его, вместо использования поведения MBR по умолчанию.

0 голосов
/ 15 сентября 2010

Это краеугольный камень Remoting: ваш Foo объект может с радостью быть MarshalByRefObject и использоваться Remoting, но должен вызываться Remoting. Вы создали канал для связи с FooManager, а не с Foo. Помните, что все типы, передаваемые в и из сеанса удаленного взаимодействия, должны быть сериализуемыми.

Вот как бы я это сделал: Иметь GetAllFooIds (), который возвращает список / массив идентификаторов в Foo, а затем использовать GetFoo, передавая идентификатор Foo.

Обновление Я думаю, что мое утверждение выше не было достаточно ясным. Дело в том, что объект может быть сериализуемым или MarshalByRefObject. В вашем случае List <> является сериализуемым и поэтому не может содержать экземпляр объекта MarshalByRefObject. Как я и предложил, я прерву звонки: один, чтобы получить идентификаторы, а другой, чтобы добраться до отдельного предмета. Медленно, да, но это единственный способ, которым я могу придумать.

0 голосов
/ 23 августа 2010

Если вы получаете список IFoos (Foo является реализующим классом), двоичный сериализатор попытается сериализовать все объекты Foo. MarshalByRefObject поддерживает генерацию прокси, которая отличается от сериализации. Отправка объектов по проводам потребует их сериализации.

Прежде всего, Foo должен быть помечен атрибутом [Serializable] или инструментом ISerializable. Все его члены также должны быть.

Ошибка, которую вы получаете, говорит о том, что серверная часть не может найти сборку, которая определяет тип. Самое простое решение - строго назвать сборку, которая определяет Foo, и добавить ее в GAC на стороне сервера.

...