如何跨 AppDomain 将引用作为方法参数传递?

Posted

技术标签:

【中文标题】如何跨 AppDomain 将引用作为方法参数传递?【英文标题】:How do I pass references as method parameters across AppDomains? 【发布时间】:2010-05-28 20:40:00 【问题描述】:

我一直在尝试使以下代码正常工作(所有内容都在同一个程序集中定义):

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."
   



我要做的是将在第一个 AppDomain 中创建的“A”实例的引用传递给子域,并让子域在第一个域上执行一个方法。在“B”代码的某个点上,我将调用“remoteObj.GetSomeData()”。必须这样做,因为“GetSomeData”方法中的“byte[]”必须在第一个 appdomain 上“计算”。 我应该怎么做才能避免异常,或者我可以做些什么来达到同样的结果?

【问题讨论】:

+1 我也是。什么版本的 CLR、Visual Studio(如果有)、C# 等?还有其他情况吗? 奇怪,我再去查一下 我可以在 .NET 4 中复制错误 【参考方案1】:

真正的根本原因是您的 dll 从两个不同应用程序域的不同位置加载。这导致 .NET 认为它们是不同的程序集,这当然意味着类型不同(即使它们具有相同的类名、命名空间等)。

通过单元测试框架运行 Jeff 的测试失败的原因是单元测试框架通常创建 AppDomain 时将 ShadowCopy 设置为“true”。但是您手动创建的 AppDomain 将默认为 ShadowCopy="false"。这将导致从不同位置加载 dll,从而导致“对象类型无法转换为目标类型”。错误。

更新:经过进一步测试,似乎归结为两个 AppDomain 之间的 ApplicationBase 不同。如果它们匹配,则上述方案有效。如果它们不同,则不会(即使我已确认 dll 已使用 windbg 从同一目录加载到两个 AppDomain 中)另外,如果我在两个 AppDomain 中都打开 ShadowCopy="true",那么它会失败带有不同的消息:“System.InvalidCastException:对象必须实现 IConvertible”。

UPDATE2:进一步阅读让我相信它与Load Contexts 有关。当您使用“From”方法之一(Assembly.LoadFrom 或 appDomain.CreateInstanceFromAndUnwrap)时,如果在正常加载路径之一(ApplicationBase 或探测路径之一)中找到程序集,则将其加载到默认加载上下文。如果在那里找不到程序集,则将其加载到 Load-From 上下文中。因此,当两个 AppDomain 都具有匹配的 ApplicationBase 时,即使我们使用“From”方法,它们也会被加载到各自的 AppDomain 的默认加载上下文中。但是当 ApplicationBase 不同时,一个 AppDomain 会将程序集放在其默认加载上下文中,而另一个将程序集放在它的 Load-From 上下文中。

【讨论】:

但即使在 setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; 之后问题仍然存在... @Shelest 尝试将 ApplicationBase 设置为 Path.GetDirectoryName("dll 文件路径") @RussellMcClure:感谢您的信息 - 它帮助我为我解决了这个问题。愿你能看看我的回答,给我你对我的解决方案的想法,因为它不是真正优雅的 IMO... @RussellMcClure:感谢您的解释!加载上下文确实是 .NET 可能认为同一个程序集不同的原因。我建议使用 Visual Studio 中的“模块”窗口查看加载模块的路径以调试问题。如果您看到在 Default 和 Load-From 上下文中加载了程序集,则可能会导致问题。 您可以在子 AppDomain 中创建对象实例,方法是在 Activator.CreateInstanceFrom() 调用中为对象类型指定当前加载的程序集的确切路径。该路径可以通过typeof(MyMarshalByRefObjType).Assembly.ManifestModule.FullyQualifiedName获取。【参考方案2】:

我可以复制这个问题,它似乎与 TestDriven.net 和/或 xUnit.net 有关。如果我将 C.Init() 作为测试方法运行,我会收到相同的错误消息。但是,如果我从控制台应用程序运行 C.Init(),则不会出现异常。

在单元测试中运行 C.Init(),您是否看到了同样的情况?

编辑:我还可以使用 NUnit 和 TestDriven.net 复制问题。我还可以使用 NUnit 运行器而不是 TestDriven.net 来复制错误。所以这个问题似乎与通过测试框架运行这段代码有关,虽然我不知道为什么。

【讨论】:

它不在测试框架中,但它帮助我找出了导致它的原因,谢谢。 我不太确定,但它与应用程序启动路径有关 你有没有想过你是如何解决这个问题的?我遇到了完全相同的问题。 @Jeff:我添加这条评论是为了“ping”你,因为我迟到了几个月。我很想看看你是否同意我的回答。 @RussellMcClure:对我来说听起来很合理。【参考方案3】:

这是对@RussellMcClure 的评论,但由于评论很复杂,我将其发布为答案:

我在一个 ASP.NET 应用程序中,关闭影子复制(这也可以解决问题)并不是一个真正的选择,但我找到了以下解决方案:

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;

这将使用主应用程序域的卷影复制目录作为应用程序库,如果启用了卷影复制,则将所有卷影复制的程序集添加到专用路径。

如果有人有更好的方法,请告诉我。

【讨论】:

以上是关于如何跨 AppDomain 将引用作为方法参数传递?的主要内容,如果未能解决你的问题,请参考以下文章

Jmeter跨线程组传递参数

C语言中如何将二维字符数组作为函数参数引用传递

在c#中将引用数组作为参数传递[重复]

跨AppDomain通信问题

在 PHP 中将数组作为参数而不是数组传递

将 lambda 作为参数传递 - 通过引用或值?