2016-07-28 4 views
2

Je suis sûr que la cause de mon problème est question similaire:Type d'objet ne peut pas être converti en cible de type lors de la traversée AppDomains

How do I pass references as method parameters across AppDomains?

et en particulier this answer, mais je ne suis pas tout à fait sûr comment le réparer. Le problème que j'ai est ceci, j'ai une extension de Visual Studio que j'écris et l'une des choses qu'il doit pouvoir charger est de charger un assembly dans un domaine séparé (ainsi je peux le vider quand je J'en ai fini avec ça) et faire des trucs avec. J'ai donc mon domaine mis en place comme ceci:

// The actual ApplicationBase of the current domain will be the one of VS and not of my plugin 
// We need our new AppDomain to be able to find our assemblies 
var p = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 
var setup = new AppDomainSetup() 
{ 
    ApplicationBase = p 
}; 
domain = AppDomain.CreateDomain("MyTest_AppDomain", AppDomain.CurrentDomain.Evidence, setup); 
var t = typeof(Proxy); 
proxy = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, 
    t.FullName, 
    false, 
    BindingFlags.Default, 
    null, 
    new object[] { assemblyPath }, 
    null, 
    null) as Proxy; 

je avais besoin de régler la ApplicationBase pour qu'il soit en mesure de charger ma classe Proxy même si elle est définie dans le même ensemble que le code ci-dessus. Sans cela, je recevais FileNotFoundExceptions

Maintenant, ma Proxy classe ressemble à quelque chose comme ceci:

public class Proxy : MarshalByRefObject 
{ 
    private Assembly _assembly; 

    private IEnumerable<Type> _types; 

    public IEnumerable<Type> Types 
    { 
     get 
     { 
      if (_types == null && _assembly != null) 
      { 
       _spiders = _assembly.GetTypes().Where(t => typeof(IFoo).IsAssignableFrom(t)).ToList(); 
      } 
      return _types; 
     } 
    } 

    public Proxy(string assemblyPath) 
    { 
     _assembly = Assembly.LoadFile(assemblyPath); 
    } 

    public IFoo GetInstanceOf(string fooType) 
    { 
     var type = Types.FirstOrDefault(t => t.FullName == fooType); 
     if (type == null) 
     { 
      throw new InvalidOperationException($"Unknown type {fooType} in assembly {_assembly.FullName}"); 
     } 
     return GetInstanceOf(type); 
    } 
    private IFoo GetInstanceOf(Type fooType) 
    { 
     var obj = Activator.CreateInstance(fooType); 
     return obj as IFoo; 
    } 

    public override object InitializeLifetimeService() 
    { 
     return null; 
    } 
} 

Retour dans la même classe qui a créé la nouvelle AppDomain et l'instance de Proxy dans le nouveau AppDomain j'ai quelque chose comme ceci:

public IFoo GetInstanceOf(Type type) 
{ 
    var name = type.FullName; 
    var foo = proxy.GetInstanceOf(name); 
    return foo; 
} 

au départ, j'ai essayé d'appeler proxy.GetInstanceOf passer directement le type, mais cela ne fonctionnait pas probablement fo r la même raison que le code que j'ai ne fonctionne pas. Quand je reçois à la ligne var foo = proxy.GetInstanceOf... ligne, je reçois un ArgumentException avec le message:

Type d'objet ne peut pas être converti en type de cible.

Je crois que le problème est parce que le IFoo dans MyTest_AppDomain n'est pas considéré comme le même IFoo que dans DefaultDomain, mais je ne suis pas sûr de ce que la bonne façon de résoudre ce problème est.

Il peut être pertinent que IFoo lui-même soit défini dans un troisième ensemble distinct que mon projet VSIX et l'assemblage que j'essaie de charger dans un domaine distinct font référence. J'ai besoin de convaincre, je pense, MyTest_AppDomain de charger le même assemblage IFoo à partir du même emplacement que DefaultDomain, ou bien de le convaincre qu'ils ne font qu'un.

Remarque: Toutes mes classes qui implémentent IFoo héritent d'une base abstraite FooBase qui elle-même hérite de MarshalByRefObject.

Modifier Penser plus sur ce mon problème vient peut-être ici:

_assembly = Assembly.LoadFile(assemblyPath); 

Le assemblyPath il y a un chemin différent du ApplicationBase et ce chemin a sa propre version de foo.dll (où IFoo est défini). Alors peut-être qu'il charge à partir de là avant le chargement de ApplicationBase?En Proxy.GetInstanceOf je peux regarder typeof(IFoo).Assembly.Location et il me donne le chemin prévu qui a été placé dans ApplicationBase, mais je ne pense pas quand mon assemblée charge foo.dll à partir de là.

+0

Voulez-vous que IFoo soit ajouté par référence ou prévoyez-vous une nouvelle copie dans votre domaine d'application? Vous faites déjà le premier avec MarshalByRef donc je suppose que vous voulez une nouvelle copie (mais en demandant juste au cas où). Assurez-vous que tout ce qui est implémenté par IFoo est sérialisable car l'instance doit être sérialisée et désérialisée lorsqu'elle traverse les domaines d'application. – Igor

+0

@Igor: Je souhaite créer une instance dans le nouveau domaine de l'application et renvoyer une référence à ce domaine vers le domaine par défaut afin que je puisse appeler des méthodes et m'abonner à certains événements. –

+0

Cela ne fonctionnera pas (par défaut). Les domaines d'application utilisent .Net remoting pour communiquer entre eux. Les instances qui sont transmises entre les domaines d'application sont essentiellement des copies. Si vous retournez 'IFoo' vous obtenez une copie de' IFoo', pas une référence à l'instance créée dans votre autre domaine d'application (par défaut) donc vous ne pouvez pas vous abonner aux événements sur IFoo ou appeler des méthodes et attendre l'instance dans votre créé un domaine d'application pour gérer ces méthodes. (plus à venir ...) – Igor

Répondre

1

J'ai mis en place un échantillon de travail rapide pour vous. Cela fait ce que vous attendez, IFoo est marshalé par ref à l'appelant.

using System; 
using System.Reflection; 

namespace AppdomainTesting 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var p = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 
      var setup = new AppDomainSetup() 
      { 
       ApplicationBase = p 
      }; 
      IProxy proxy; 
      var domain = AppDomain.CreateDomain("MyTest_AppDomain", AppDomain.CurrentDomain.Evidence, setup); 
       var t = typeof(Proxy); 
      proxy = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, 
       t.FullName, 
       false, 
       BindingFlags.Default, 
       null, 
       null, 
       null, 
       null) as IProxy; 

      // works 
      var typeofFoo = typeof(Foo); 
      var foo = proxy.GetFoo(new TypeDef() { Asembly = typeofFoo.Assembly.FullName, FullTypeName = typeofFoo.FullName }); 
      Console.WriteLine(foo.Domain); 

      // Type also implements Serializable attribute (my mistake, I thought it did not) 
      foo = proxy.GetFoo(typeofFoo); 
      Console.WriteLine(foo.Domain); 

      Console.WriteLine(); 
      Console.WriteLine("ENTER to exit"); 
      Console.ReadLine(); 
     } 
    } 

    public interface IFoo 
    { 
     string Domain { get; } 
    } 

    public class Foo : MarshalByRefObject, IFoo 
    { 
     public string Domain 
     { 
      get { return System.AppDomain.CurrentDomain.FriendlyName; } 
     } 
    } 
    public interface IProxy 
    { 
     IFoo GetFoo(TypeDef typeToGet); 
     IFoo GetFoo(Type type); 
    } 
    [Serializable] 
    public class TypeDef 
    { 
     public string Asembly { get; set; } 
     public string FullTypeName { get; set; } 
    } 

    public class Proxy : MarshalByRefObject, IProxy 
    { 
     public IFoo GetFoo(TypeDef typeToGet) 
     { 
      var type = Type.GetType(typeToGet.FullTypeName + ", " + typeToGet.Asembly, true); 
      var instance = Activator.CreateInstance(type) as IFoo; 
      return instance; 
     } 
     public IFoo GetFoo(Type type) 
     { 
      var instance = Activator.CreateInstance(type) as IFoo; 
      return instance; 
     } 
    } 
}