2010-11-15 3 views
5

У меня есть приложение .NET, в котором сборки в отдельных AppDomains должны делить сериализованные объекты, которые передаются по значению.Как передать неизвестный тип между двумя .NET AppDomains?

Обе сборки ссылаются на общий узел, который определяет базовый класс для класса сервера, а также определяет базовый класс для типа entiy, который будет передан между доменами:

public abstract class ServerBase : MarshalByRefObject 
{ 
    public abstract EntityBase GetEntity(); 
} 

[Serializable] 
public abstract class EntityBase 
{ 
} 

Узел сервера определяет класс сервера и конкретная реализация типа объекта:

public class Server : ServerBase 
{ 
    public override EntityBase GetEntity() 
    { 
     return new EntityItem(); 
    } 
} 

[Serializable] 
public class EntityItem : EntityBase 
{ 
} 

Узел клиента создает AppDomain, в котором сборка сервера будет организована и использует экземпляр класса сервера, чтобы запросить конкретный экземпляр типа объекта:

class Program 
{ 
    static void Main() 
    { 
     var domain = AppDomain.CreateDomain("Server"); 

     var server = (ServerBase)Activator.CreateInstanceFrom(
      domain, 
      @"..\..\..\Server\bin\Debug\Server.dll", 
      "Server.Server").Unwrap(); 

     var entity = server.GetEntity(); 
    } 
} 

Unfortnately, этот подход не с SerializationException, потому что сборка клиент не имеет непосредственного знания конкретного типа, который возвращается.

Я читал, что удаленная работа .NET поддерживает неизвестные типы при использовании двоичной сериализации, но я не уверен, относится ли это к моей настройке или как ее настроить.

В качестве альтернативы, существует ли другой способ передачи неизвестного конкретного типа с сервера на клиент, учитывая, что клиенту требуется только доступ к нему через свой известный интерфейс базового класса.

Спасибо за ваши советы,

Tim

EDIT:

По просьбе Ганса, вот сообщение об исключении и трассировки стека.

SerializationException 
Type is not resolved for member 'Server.EntityItem,Server, Version=1.0.0.0,Culture=neutral, PublicKeyToken=null'. 

at Interop.ServerBase.GetEntity() 
at Client.Program.Main() in C:\Users\Tim\Visual Studio .Net\Solutions\MEF Testbed\Client\Program.cs:line 12 
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) 
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) 
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 
at System.Threading.ThreadHelper.ThreadStart_Context(Object state) 
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) 
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 
at System.Threading.ThreadHelper.ThreadStart() 

ответ

2

Это не удается, так как CLR просто не может надеяться найти сборку, вы положите ее в неоправданное место. Тривиально решить эту проблему, добавив ссылку на сборку и установив для нее свойство Copy Local значение True, чтобы server.dll скопировался в ваш каталог сборки. Если вы хотите сохранить его там, где он есть, вам придется реализовать AppDomain.AssemblyResolve, чтобы помочь CLR найти его.

+0

Спасибо Хансу. Ваше предложение очень разумно, но я должен быть уверен, что он не вводит дополнительную проблему. То, что я описал, является частью сценария песочницы, поэтому я не хочу, чтобы CLR загружал неизвестную сборку в основной AppDomain (который имеет более широкие разрешения), если это может поставить под угрозу безопасность. У вас есть мнение по этому поводу? Еще раз спасибо. –

+0

Используйте интерфейс, объявленный в своей собственной сборке и ссылающийся на оба. –

+0

ОК, я изменил класс EntityBase как интерфейс и по-прежнему находится в общей сборке, но исключение все еще бросается (и предположительно по той причине, что вы уже заявили), что переданный объект неизвестен клиент). –

0

Я думаю, что у меня есть решение, благодаря нынешней должности, и это один и его принял ответ: AppDomain.Load() fails with FileNotFoundException

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

Решение: создать конкретный тип в общей сборке, которая наследуется от MarshalByRefObject и реализует интерфейс вашего сервера. Этот конкретный тип - это прокси, который может быть сериализован/десериализирован между AppDomains, потому что ваше основное приложение знает его определение. Вам больше не нужно наследовать от MarshalByRefObject в вашем классе ServerBase.

// - MUST be serializable, and MUSNT'T use unknown types for main App 
    [Serializable] 
    public class Query 
    { 
    ... 
    } 

    public interface IServerBase 
    { 
     string Execute(Query q); 
    } 

    public abstract class ServerBase : IServerBase 
    { 
     public abstract string Execute(Query q); 
    } 

// Our CUSTOM PROXY: the concrete type which will be known from main App 
[Serializable] 
public class ServerBaseProxy : MarshalByRefObject, IServerBase 
{ 
    private IServerBase _hostedServer; 

    /// <summary> 
    /// cstor with no parameters for deserialization 
    /// </summary> 
    public ServerBaseProxy() 
    { 

    } 

    /// <summary> 
    /// Internal constructor to use when you write "new ServerBaseProxy" 
    /// </summary> 
    /// <param name="name"></param> 
    public ServerBaseProxy(IServerBase hostedServer) 
    { 
     _hostedServer = hostedServer; 
    }  

    public string Execute(Query q) 
    { 
     return(_hostedServer.Execute(q)); 
    } 

} 

Примечание: для того, чтобы передавать и принимать данные, каждый тип, объявленный в IServer должен сериализовать (например: с [Serializable] атрибутом)

Затем, вы можете использовать метод нашел в предыдущая ссылка «Loader class». Вот мой модифицированный класс Loader, который создание экземпляра конкретного типа в общей сборке, и возвращает прокси-сервер для каждого плагина:

/// <summary> 
/// Source: https://stackoverflow.com/questions/16367032/appdomain-load-fails-with-filenotfoundexception 
/// </summary> 
public class Loader : MarshalByRefObject 
{ 

    /// <summary> 
    /// Load plugins 
    /// </summary> 
    /// <param name="assemblyName"></param> 
    /// <returns></returns> 
    public IPlugin[] LoadPlugins(string assemblyPath) 
    { 
     List<PluginProxy> proxyList = new List<PluginProxy>(); // a proxy could be transfered outsite AppDomain, but not the plugin itself ! https://stackoverflow.com/questions/4185816/how-to-pass-an-unknown-type-between-two-net-appdomains 

     var assemb = Assembly.LoadFrom(assemblyPath); // use Assembly.Load if you want to use an Assembly name and not a path 

     var types = from type in assemb.GetTypes() 
        where typeof(IPlugin).IsAssignableFrom(type) 
        select type; 

     var instances = types.Select(
      v => (IPlugin)Activator.CreateInstance(v)).ToArray(); 

     foreach (IPlugin instance in instances) 
     { 
      proxyList.Add(new PluginProxy(instance)); 
     } 
     return (proxyList.ToArray()); 
    } 

} 

Затем в главном приложении, я также использовать код «dedpichto» и " James Thurley ", чтобы создать AppDomain, запустить и вызвать класс Loader. Я тогда в состоянии использовать мой прокси, как это был мой плагин, потому что .NET создает «прозрачный прокси» в связи с MarshalByRefObject:

/// <see cref="https://stackoverflow.com/questions/4185816/how-to-pass-an-unknown-type-between-two-net-appdomains"/> 
public class PlugInLoader 
{  

    /// <summary> 
    /// https://stackoverflow.com/questions/16367032/appdomain-load-fails-with-filenotfoundexception 
    /// </summary> 
    public void LoadPlugins(string pluginsDir) 
    { 
     // List all directories where plugins could be 
     var privatePath = ""; 
     var paths = new List<string>(); 
     List<DirectoryInfo> dirs = new DirectoryInfo(pluginsDir).GetDirectories().ToList(); 
     dirs.Add(new DirectoryInfo(pluginsDir)); 
     foreach (DirectoryInfo d in dirs) 
      privatePath += d.FullName + ";"; 
     if (privatePath.Length > 1) privatePath = privatePath.Substring(0, privatePath.Length - 1); 

     // Create AppDomain ! 
     AppDomainSetup appDomainSetup = AppDomain.CurrentDomain.SetupInformation; 
     appDomainSetup.PrivateBinPath = privatePath; 

     Evidence evidence = AppDomain.CurrentDomain.Evidence; 
     AppDomain sandbox = AppDomain.CreateDomain("sandbox_" + Guid.NewGuid(), evidence, appDomainSetup); 

     try 
     { 
      // Create an instance of "Loader" class of the shared assembly, that is referenced in current main App 
      sandbox.Load(typeof(Loader).Assembly.FullName); 

      Loader loader = (Loader)Activator.CreateInstance(
       sandbox, 
       typeof(Loader).Assembly.FullName, 
       typeof(Loader).FullName, 
       false, 
       BindingFlags.Public | BindingFlags.Instance, 
       null, 
       null, 
       null, 
       null).Unwrap(); 

      // Invoke loader in shared assembly to instanciate concrete types. As long as concrete types are unknown from here, they CANNOT be received by Serialization, so we use the concrete Proxy type. 

      foreach (var d in dirs) 
      { 
       var files = d.GetFiles("*.dll"); 
       foreach (var f in files) 
       { 
        // This array does not contains concrete real types, but concrete types of "my custom Proxy" which implements IPlugin. And here, we are outside their AppDomain, so "my custom Proxy" is under the form of a .NET "transparent proxy" (we can see in debug mode) generated my MarshalByRefObject. 
        IPlugin[] plugins = loader.LoadPlugins(f.FullName); 
        foreach (IPlugin plugin in plugins) 
        { 
         // The custom proxy methods can be invoked ! 
         string n = plugin.Name.ToString(); 
         PluginResult result = plugin.Execute(new PluginParameters(), new PluginQuery() { Arguments = "", Command = "ENUMERATE", QueryType = PluginQueryTypeEnum.Enumerate_Capabilities }); 
         Debug.WriteLine(n); 
        }      
       } 
      } 
     } 
     finally 
     { 
      AppDomain.Unload(sandbox); 
     } 
    } 
} 

Это действительно трудно найти рабочее решение, но мы, наконец, можем держать экземпляры пользовательских прокси из наших конкретных типов, созданных в другом AppDomain, и использовать их так, как если бы они были доступны в основном приложении.

Надеюсь, что это (огромный ответ) поможет!