2010-11-15 10 views
5

Tengo una aplicación .NET en la que los ensamblajes en AppDomains separados deben compartir objetos serializados que se pasan por valor. Referencia¿Cómo pasar un tipo desconocido entre dos .NET AppDomains?

Ambos conjuntos de un conjunto compartido que define la clase base para la clase de servidor y también define la clase base para el tipo entiy que será pasado entre dominios:

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

[Serializable] 
public abstract class EntityBase 
{ 
} 

El conjunto de servidor define la clase de servidor y una Implementación concreta del tipo de entidad:

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

[Serializable] 
public class EntityItem : EntityBase 
{ 
} 

el montaje de cliente crea el AppDomain en el que se encuentra alojado el conjunto de servidor y utiliza una instancia de la clase del servidor para solicitar una instancia concreta del tipo de entidad:

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, este enfoque falla con un SerializationException porque el ensamblaje cliente no tiene conocimiento directo del tipo concreto que se está devolviendo.

He leído que .NET remoting admite tipos desconocidos cuando se utiliza la serialización binaria, pero no estoy seguro de si esto se aplica a mi configuración o cómo configurarla.

Alternativamente, ¿hay alguna otra manera de pasar un tipo concreto desconocido del servidor al cliente, dado que el cliente solo necesita acceder a él a través de su interfaz de clase base conocida.

Gracias por su consejo,

Tim

EDIT:

a lo solicitado por Hans, aquí está el mensaje de excepción y Seguimiento de la pila.

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

Respuesta

2

esto falla porque el CLR simplemente no tiene ninguna esperanza de ser capaz de encontrar el montaje, que lo puso en un lugar imposible de encontrar. Resuelva esto de manera trivial agregando una referencia al ensamblaje y estableciendo su propiedad Copy Local en True para que server.dll se copie en su directorio de compilación. Si desea mantenerlo donde está, deberá implementar AppDomain.AssemblyResolve para ayudarlo a encontrarlo.

+0

Gracias Hans. Su sugerencia es muy razonable, pero necesito estar seguro de que no presenta un problema adicional. Lo que describí es parte de un escenario de sandboxing, por lo que no quiero que CLR cargue el ensamblado desconocido en el AppDomain primario (que tiene permisos más amplios) si es probable que comprometa la seguridad. ¿Tienes una opinión sobre esto? Gracias de nuevo. –

+0

Use una interfaz, declarada en su propio ensamblado y referenciada por ambos. –

+0

Bien, cambié la clase EntityBase para que sea una interfaz y reside, como antes, en el ensamblado compartido, pero la excepción todavía se lanza (y presumiblemente por la razón que ya ha indicado, que el objeto pasado es desconocido por el cliente). –

0

creo que tengo una solución gracias al puesto actual, y éste y su respuesta aceptada: AppDomain.Load() fails with FileNotFoundException

Lo primero, me cree que debería usar una interfaz en lugar de una clase base para ser su manejador. La interfaz debe declararse en la clase base, y luego solo la usarás.

Solución: cree un tipo de hormigón en el conjunto compartido, que hereda de MarshalByRefObject, e implementa su interfaz de servidor. Este tipo concreto es un proxy que se puede serializar/deserializar entre AppDomains porque su aplicación principal conoce su definición. Ya no es necesario que heredes desde MarshalByRefObject en tu clase 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)); 
    } 

} 

Nota: con el fin de enviar y recibir datos, cada tipo declarado en IServer debe ser serializable (por ejemplo: con [Serializable] atributo)

A continuación, puede utilizar el método que se encuentra en enlace anterior "Loader class". Aquí es mi clase Loader modificado que instanciate tipo concreto de ensamblado compartido, y devuelve un proxy para cada plugin:

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

} 

Entonces, en la aplicación maestra, también utilizo el código de "dedpichto" y " James Thurley "para crear AppDomain, instanciar e invocar la clase Loader. Soy entonces capaz de usar mi proxy ya que era mi complemento, porque .NET crea un "proxy transparente", debido a 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); 
     } 
    } 
} 

Es realmente difícil encontrar una solución de trabajo, pero por fin podemos conservar instancias de proxies personalizados de nuestros tipos concretos instanciados en otro AppDomain y utilícelos como si estuvieran disponibles en la aplicación principal.

Espero que esto (gran respuesta) ayude!

Cuestiones relacionadas