Wrapping __TransparentProxy of RemotingProxy in another Proxy throws RemotingException
Asked Answered
V

1

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);
    }
}
Vanatta answered 30/7, 2018 at 14:36 Comment(2)
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.Solan
I tried that before CanCastTo gets called 4 times on the DataImpl proxy with the fromType arguments: 2xContextBoundObject then 2xAppDomain. Handing the call down to the impl proxy results in all 4 returning false.Vanatta
M
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.

Margarettemargarida answered 9/8, 2018 at 18:1 Comment(2)
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).Vanatta
@Vanatta 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.Margarettemargarida

© 2022 - 2024 — McMap. All rights reserved.