How do I pass references as method parameters across AppDomains?
Asked Answered
A

3

9

I have been trying to get the following code to work(everything is defined in the same assembly) :

namespace SomeApp{

public class A : MarshalByRefObject
{
   public byte[] GetSomeData() { // }
}

public class B : MarshalByRefObject
{
   private A remoteObj;

   public void SetA(A remoteObj)
   {
      this.remoteObj = remoteObj;
   }
}

public class C
{
   A someA = new A();
   public void Init()
   {
       AppDomain domain = AppDomain.CreateDomain("ChildDomain");
       string currentAssemblyPath = Assembly.GetExecutingAssembly().Location;
       B remoteB = domain.domain.CreateInstanceFromAndUnwrap(currentAssemblyPath,"SomeApp.B") as B;
       remoteB.SetA(someA); // this throws an ArgumentException "Object type cannot be converted to target type."
   }
}

}

What I'm trying to do is pass a reference of an 'A' instance created in the first AppDomain to the child domain and have the child domain execute a method on the first domain. In some point on 'B' code I'm going to call 'remoteObj.GetSomeData()'. This has to be done because the 'byte[]' from 'GetSomeData' method must be 'calculated' on the first appdomain. What should I do to avoid the exception, or what can I do to achieve the same result?

Agnew answered 28/5, 2010 at 20:40 Comment(3)
+1 For me too. What versions of CLR, Visual Studio (if any), C#, etc? Any other circumstances?Tune
strange, I'm going to check againAgnew
I'm able to duplicate the error in .NET 4Inebriety
I
2

I can duplicate the issue, and it seems to be related to TestDriven.net and/or xUnit.net. If I run C.Init() as a test method, I get the same error message. However, if I run C.Init() from a console application, I do not get the exception.

Are you seeing the same thing, running C.Init() from a unit test?

Edit: I'm also able to duplicate the issue using NUnit and TestDriven.net. I'm also able to duplicate the error using the NUnit runner instead of TestDriven.net. So the problem seems to be related to running this code through a testing framework, though I'm not sure why.

Inebriety answered 28/5, 2010 at 21:9 Comment(5)
It was not in a testing framework, but it helped me figure what was causing it, thanks.Agnew
I'm not exactly sure, but it had something to do with the application startup pathAgnew
Did you ever figure out how you fixed this? I am having exactly the same problem.Diarmid
@Jeff: I'm adding this comment to "ping" you since I'm a few months late to this party. I'd be interested to see if you agree with my answer.Inkhorn
@RussellMcClure: Sounds reasonable to me.Inebriety
I
12

The actual root cause was your dll was getting loaded from different locations in the two different app domains. This causes .NET to think they are different assemblies which of course means the types are different (even though they have the same class name, namespace etc).

The reason Jeff's test failed when run through a unit test framework is because unit test frameworks generally create AppDomains with ShadowCopy set to "true". But your manually created AppDomain would default to ShadowCopy="false". This would cause the dlls to be loaded from different locations which leads to the nice "Object type cannot be converted to target type." error.

UPDATE: After further testing, it does seem to come down to the ApplicationBase being different between the two AppDomains. If they match, then the above scenario works. If they are different it doesn't (even though I've confirmed that the dll is loaded into both AppDomains from the same directory using windbg) Also, if I turn on ShadowCopy="true" in both of my AppDomains, then it fails with a different message: "System.InvalidCastException: Object must implement IConvertible".

UPDATE2: Further reading leads me to believe it is related to Load Contexts. When you use one of the "From" methods (Assembly.LoadFrom, or appDomain.CreateInstanceFromAndUnwrap), if the assembly is found in one of the normal load paths (the ApplicationBase or one of the probing paths) then is it loaded into the Default Load Context. If the assembly isn't found there, then it is loaded into the Load-From Context. So when both AppDomains have matching ApplicationBase's, then even though we use a "From" method, they are both loaded into their respective AppDomain's Default Load Context. But when the ApplicationBase's are different, then one AppDomain will have the assembly in its Default Load Context while the other has the assembly in it's Load-From Context.

Inkhorn answered 18/10, 2011 at 23:44 Comment(5)
but even after setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; issue still presents...Passant
@Passant Try setting your ApplicationBase to Path.GetDirectoryName("path of dll file")Erepsin
@RussellMcClure: Thanks for your information - it helped me solve this for me. May you can have a look at my answer and give me your thoughts about my solution as it is not really elegant IMO...Delanos
@RussellMcClure: I'd like to thank you for your explanation! Load contexts is indeed why .NET might think the same assembly is different. I'd recommend looking at the path of loaded modules using the 'Modules' window in Visual Studio to debug issues. If you see an assembly loaded in Default and Load-From context, that is likely going to cause issues.Gelatinize
You can create an object instance in a child AppDomain by specifying the exact path to the currently-loaded assembly for the object's type in your Activator.CreateInstanceFrom() call. This path can be obtained via typeof(MyMarshalByRefObjType).Assembly.ManifestModule.FullyQualifiedName.Knighterrantry
I
2

I can duplicate the issue, and it seems to be related to TestDriven.net and/or xUnit.net. If I run C.Init() as a test method, I get the same error message. However, if I run C.Init() from a console application, I do not get the exception.

Are you seeing the same thing, running C.Init() from a unit test?

Edit: I'm also able to duplicate the issue using NUnit and TestDriven.net. I'm also able to duplicate the error using the NUnit runner instead of TestDriven.net. So the problem seems to be related to running this code through a testing framework, though I'm not sure why.

Inebriety answered 28/5, 2010 at 21:9 Comment(5)
It was not in a testing framework, but it helped me figure what was causing it, thanks.Agnew
I'm not exactly sure, but it had something to do with the application startup pathAgnew
Did you ever figure out how you fixed this? I am having exactly the same problem.Diarmid
@Jeff: I'm adding this comment to "ping" you since I'm a few months late to this party. I'd be interested to see if you agree with my answer.Inkhorn
@RussellMcClure: Sounds reasonable to me.Inebriety
D
1

This is a comment to @RussellMcClure but as it is to complex for a comment I post this as an answer:

I am inside an ASP.NET application and turning off shadow-copy (which would also solve the problem) is not really an option, but I found the following solution:

AppDomainSetup adSetup = new AppDomainSetup();
if (AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles == "true")
{
    var shadowCopyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (shadowCopyDir.Contains("assembly"))
        shadowCopyDir = shadowCopyDir.Substring(0, shadowCopyDir.LastIndexOf("assembly"));

    var privatePaths = new List<string>();
    foreach (var dll in Directory.GetFiles(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, "*.dll"))
    {
        var shadowPath = Directory.GetFiles(shadowCopyDir, Path.GetFileName(dll), SearchOption.AllDirectories).FirstOrDefault();
        if (!String.IsNullOrWhiteSpace(shadowPath))
            privatePaths.Add(Path.GetDirectoryName(shadowPath));
    }

    adSetup.ApplicationBase = shadowCopyDir;
    adSetup.PrivateBinPath = String.Join(";", privatePaths);
}
else
{
    adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
    adSetup.PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
}

This will use the shadow-copy directory of the main app-domain as the application-base and add all shadow-copied assemblies to the private path if shadow-copy is enabled.

If someone has a better way of doing this please tell me.

Delanos answered 27/7, 2014 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.