如何在 AppDomain 之间传递引用作为方法参数?

发布于 2024-09-03 05:41:20 字数 929 浏览 6 评论 0原文

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

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[]”。 我应该怎样做才能避免异常,或者我可以怎样做才能达到相同的结果?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

向日葵 2024-09-10 05:41:20

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

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

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

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

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.

她比我温柔 2024-09-10 05:41:20

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

您是否看到同样的事情,从单元测试运行 C.Init() ?

编辑:我还可以使用 NUnit 和 TestDriven.net 复制该问题。我还可以使用 NUnit 运行程序而不是 TestDriven.net 来复制错误。所以问题似乎与通过测试框架运行此代码有关,尽管我不确定为什么。

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.

妄想挽回 2024-09-10 05:41:20

这是对 @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;
}

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文