6

Goal

I have a couple of interfaces and some dlls that provide implementations for these interfaces. I want to load the implementation into a new AppDomain (so I can unload the dll later) and instatiate the implementation in the new AppDomain and then use a clientside(here the default AppDomain) proxy to wrap the actual implementation object. The goal is to create these ClientProxy instances once and change their actual implementations whenever while not loading the implementations assembly into the default AppDomain.

Problem

When calling a method on the ClientProxy __TransparentProxy object that gets another ClientProxy as argument I get the following Exceptions:

System.Runtime.Remoting.RemotingException: 'The argument type 'System.MarshalByRefObject' cannot be converted into parameter type 'IData'.'

With inner Exception:

InvalidCastException: Object must implement IConvertible.

When passing the __TransparentProxy obtained directly from the Server AppDomain the ClientProxy works.

Setup

Cloneable from: https://github.com/mailgerigk/remoting

Interfaces:

public interface IData
{
    int Foo { get; set; }
}

public interface ILogic
{
    void Update(IData data);
}

Interface Impl in _impl.dll:

public class DataImpl : MarshalByRefObject, IData
{
    public int Foo { get; set; }
}

public class LogicImpl : MarshalByRefObject, ILogic
{
    public void Update(IData data)
    {
        data.Foo++;
    }
}

Serverside AssemblyLoader:

class AssemblyLoader : MarshalByRefObject
{
    public Assembly Assembly { get; private set; }

    public AssemblyLoader(string assemblyFile)
    {
        Assembly = Assembly.LoadFrom(assemblyFile);
    }

    public object CreateInstance(string typeName)
    {
        return Activator.CreateInstance(Assembly.GetType(typeName));
    }
}

ClientProxy:

class ClientProxy : RealProxy
{
    private RealProxy innerProxy;

    public ClientProxy(Type interfaceType, object proxyObject)
        : base(interfaceType)
    {
        SetInnerProxy(proxyObject);
    }

    public void SetInnerProxy(object proxyObject)
    {
        innerProxy = RemotingServices.GetRealProxy(proxyObject);
    }

    public override IMessage Invoke(IMessage msg)
    {
        return innerProxy.Invoke(msg);
    }
}

Main:

class Program
{
    static void Main(string[] args)
    {
        var app = AppDomain.CreateDomain("ImplDomain", null,
            AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.RelativeSearchPath,
            true);

        var assmblyLoader = app.CreateInstanceFromAndUnwrap(
            typeof(AssemblyLoader).Assembly.Location, typeof(AssemblyLoader).FullName,
            false, BindingFlags.CreateInstance, null,
            new object[]
            {
                "_impl.dll"
            },
            null, null) as AssemblyLoader;

        var dataImpl = assmblyLoader.CreateInstance("DataImpl") as IData;
        var logicImpl = assmblyLoader.CreateInstance("LogicImpl") as ILogic;

        logicImpl.Update(dataImpl); // Works
        Console.WriteLine(dataImpl.Foo); // prints 1

        var clientDataProxy = new ClientProxy(typeof(IData), dataImpl);
        var clientDataImpl = clientDataProxy.GetTransparentProxy() as IData;

        var clientLogicProxy = new ClientProxy(typeof(ILogic), logicImpl);
        var clientLogicImpl = clientLogicProxy.GetTransparentProxy() as ILogic;

        clientLogicImpl.Update(dataImpl); // Works
        Console.WriteLine(clientDataImpl.Foo); // prints 2

        clientLogicImpl.Update(clientDataImpl); // throws System.Runtime.Remoting.RemotingException
        Console.WriteLine(clientDataImpl.Foo);
    }
}
prydain
  • 365
  • 3
  • 11
  • This very vaguely rings a bell from somewhere when I was playing with proxies. I think you need to implement `IRemotingTypeInfo` and implement the `CanCastTo` member (even though you've already specified the `IData` interface in the constructor). Disclaimer: I could be wildly off base. Even if I am, though, you may be able to gleam more about what's happening by setting a breakpoint there. – Jeroen Mostert Jul 30 '18 at 15:05
  • I tried that before `CanCastTo` gets called 4 times on the `DataImpl` proxy with the `fromType` arguments: 2x`ContextBoundObject` then 2x`AppDomain`. Handing the call down to the impl proxy results in all 4 returning `false`. – prydain Jul 30 '18 at 16:56

1 Answers1

2

Without knowing your reasoning, from the example code provided, the ClientProxy class appears unnecessary since you could get the same behaviour just by using a RealProxy directly:

var clientDataProxy = RemotingServices.GetRealProxy(dataImpl);

That being said, you can of course create your own concrete RealProxy and have it work as expected - you are just missing an override to get the TransparentProxy from the innerProxy:

class ClientProxy : RealProxy
{
    ... everything else ...

    public override object GetTransparentProxy() => innerProxy.GetTransparentProxy();

    public object GetOuterTransparentProxy() => base.GetTransparentProxy();
}

...
var clientDataProxy = new ClientProxy(typeof(IData), dataImpl);
var clientDataImpl = clientDataProxy.GetOuterTransparentProxy() as IData;

var clientLogicProxy = new ClientProxy(typeof(ILogic), logicImpl);
var clientLogicImpl = clientLogicProxy.GetOuterTransparentProxy() as ILogic;
...

As it stands, your ClientProxy is returning a TransparentProxy to the outer object.

Edit: Based on the explanation in the comments I have added a secondary GetOuterTransparentProxy method that returns the outer base implementation. This would still allow the OOTB RealProxy.Invoke implementation to be used, while exposing the outer TransparentProxy to be reused.

Andrew Hanlon
  • 7,271
  • 4
  • 33
  • 53
  • The reason for the `ClientProxy` is the ability to load another Impl Assembly without invalidating the `clientDataImpl/clientLogicImpl` instances (and all the references to them) and to then unload the AppDomain with the previously used Impl Assembly. By returning the `innerProxy.GetTransparentProxy()` the references to the TransparentProxy will become invalid when unloading their Remote AppDomain. What I want to do is "delegate" all method calls to the currently loaded Impl Proxy. This works as long as I don't pass another `ClientProxy` as method argument (see clientLogicImpl.Update calls). – prydain Aug 10 '18 at 00:26
  • @prydain That helps to explain the desired result; however I believe that would require overriding the Invoke method to understand the configuration (not a small task). I have a simple alternative to expose both proxies shown in the Edit above. – Andrew Hanlon Aug 15 '18 at 15:43