2

I'm sure the root cause of my problem is this similar question:

How do I pass references as method parameters across AppDomains?

and in particular this answer, but I'm not quite sure how to fix it.

The problem I have is this, I have a Visual Studio extension that I'm writing and one of the things it needs to be able to do is load an assembly into a separate domain (so I can dump it when I'm done with it) and do some stuff with it. So I have my domain set up like this:

// 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;

I needed to set the ApplicationBase for it to be able to load my class Proxy even though it's defined in the same assembly as the above code. Without that, I was getting FileNotFoundExceptions

Now my Proxy class looks something like this:

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

Back in the same class that created the new AppDomain and the instance of Proxy in the new AppDomain I have something like this:

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

Initially I tried to call proxy.GetInstanceOf passing the type directly, but that wasn't working probably for the same reason as the code I have doesn't work. When I get to the line var foo = proxy.GetInstanceOf... line I get an ArgumentException with the message:

Object type cannot be converted to target type.

I believe the problem is because the IFoo in MyTest_AppDomain isn't considered to be the same IFoo as in DefaultDomain, but I'm not sure what the correct way to fix this is.

It may be relevant that IFoo itself is defined in a separate third assembly that both my VSIX project and the assembly I'm trying to load in a separate domain are referencing. I need to convince, I think, MyTest_AppDomain to load the same IFoo assembly from the same location as DefaultDomain, or else convince it that they are one and the same.

Note: All my classes that implement IFoo inherit from an abstract base FooBase which itself inherits from MarshalByRefObject.

Edit Thinking more about this my problem might be coming from here:

_assembly = Assembly.LoadFile(assemblyPath);

The assemblyPath there is a different path to the ApplicationBase and that path has it's own version of foo.dll (where IFoo is defined). So perhaps it is loading from there before loading from ApplicationBase? In Proxy.GetInstanceOf I can look at typeof(IFoo).Assembly.Location and it gives me the expected path that was set in ApplicationBase, but I don't think when my assembly loads it's getting foo.dll from there.

Community
  • 1
  • 1
Matt Burland
  • 44,552
  • 18
  • 99
  • 171
  • Do you want IFoo to be bassed by reference or do you expect a new copy in your application domain? You are already doing the former with MarshalByRef so I am assuming you want a new copy (but asking just in case). Make sure that whatever is implemented by IFoo is serializable as the instance should be serialized and deserialized as it crosses application domains. – Igor Jul 28 '16 at 17:41
  • @Igor: I want to create an instance in the new app domain and pass a reference to that back to the default domain so I can call methods on it and subscribe to some events. – Matt Burland Jul 28 '16 at 17:50
  • That wont work (by default). Application domains use .Net remoting to communicate between each other. Instances that are passed between application domains are essentially copies. If you pass back `IFoo` you get a copy of `IFoo`, not a reference to the instance created in your other application domain (by default) so you could not subscribe to events on IFoo or call methods and expect the instance in your created app domain to handle those methods. (more to come...) – Igor Jul 28 '16 at 17:55
  • But it should be a proxy, no? I know it's not the same object, but it is a proxy that I can work with (almost) as if it was the same object. – Matt Burland Jul 28 '16 at 17:57
  • However the types that implement `IFoo` could inherit from MarshalByRef like your proxy does in which case you could access the reference in your default application domain. As far as events go here is something on [SO](http://stackoverflow.com/a/5871944/1260204), I can't say much about it as I have never created an event based model that worked across application domains. Be sure that your parameters that are not MarshalByRef all use the [Serializable] attribute on the class and are public . – Igor Jul 28 '16 at 17:58
  • I was about to mention that. My classes that implement `IFoo` do indeed inherit from `MarshalByRefObject`, so that part *should* work (I think), if I can convince it that it's dealing with the same `IFoo` in both contexts. – Matt Burland Jul 28 '16 at 18:00
  • Also it makes sense that you cant pass System.Type as I do not believe it is marked as Serializable. You do have to make sure that the `IFoo` interface is in some common assembly (i think you mentioned that but just in case). Maybe I am not understanding at what point it is failing? – Igor Jul 28 '16 at 18:04

1 Answers1

1

I put together a quick working sample for you. This does what you would expect it to, IFoo is marshaled by ref to the caller.

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;
        }
    }
}
Igor
  • 60,821
  • 10
  • 100
  • 175