2010-05-28 10 views
7

Ich habe versucht, den folgenden Code zu arbeiten (alles in derselben Baugruppe definiert ist):Wie übergebe ich Referenzen als Methodenparameter über AppDomains?

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

} 

Was ich versuche zu tun, um eine Referenz einer ‚A‘ Instanz in dem erstellt passieren erste AppDomain für die untergeordnete Domäne und die untergeordnete Domäne führt eine Methode für die erste Domäne aus. Irgendwo auf 'B' Code werde ich 'remoteObj.GetSomeData()' aufrufen. Dies muss getan werden, weil das 'Byte []' von 'GetSomeData' Methode auf der ersten appdomain 'berechnet' werden muss. Was muss ich tun, um die Ausnahme zu vermeiden, oder was kann ich tun, um dasselbe Ergebnis zu erzielen?

+0

Ihr Code funktioniert für mich. –

+0

+1 Für mich auch. Welche Versionen von CLR, Visual Studio (falls vorhanden), C#, etc? Irgendwelche anderen Umstände? –

+0

seltsam, ich werde wieder überprüfen –

Antwort

2

Ich kann das Problem duplizieren, und es scheint zu TestDriven.net und/oder xUnit.net verwandt zu sein. Wenn ich C.Init() als eine Testmethode ausführen, erhalte ich die gleiche Fehlermeldung. Wenn ich C.Init() von einer Konsolenanwendung aus ausführen, erhalte ich jedoch nicht die Ausnahme.

Sehen Sie die gleiche Sache, wenn Sie C.Init() von einem Komponententest ausführen?

Bearbeiten: Ich bin auch in der Lage, das Problem mit NUnit und TestDriven.net zu duplizieren. Ich bin auch in der Lage, den Fehler mit dem NUnit-Runner anstelle von TestDriven.net zu duplizieren. Das Problem scheint damit zu tun zu haben, diesen Code durch ein Test-Framework laufen zu lassen, obwohl ich mir nicht sicher bin, warum.

+0

Es war nicht in einem Test-Framework, aber es half mir herauszufinden, was es verursacht, danke. –

+0

Cool, was hat es verursacht? –

+0

Ich bin mir nicht genau sicher, aber es hatte etwas mit dem Startpfad der Anwendung zu tun –

10

Die eigentliche Ursache war Ihre Dll wurde von verschiedenen Standorten in den zwei verschiedenen App-Domänen geladen. Dies führt dazu, dass .NET denkt, dass es sich um verschiedene Assemblies handelt, was natürlich bedeutet, dass die Typen unterschiedlich sind (obwohl sie den gleichen Klassennamen, Namensraum usw. haben).

Der Grund, warum Jeffs Test fehlgeschlagen ist, wenn ein Unit-Test-Framework durchlaufen wurde, ist, weil Unit-Test-Frameworks im Allgemeinen AppDomains mit ShadowCopy auf "true" setzen. Ihre manuell erstellte AppDomain würde jedoch standardmäßig auf ShadowCopy = "false" gesetzt. Dies würde dazu führen, dass die DLLs von verschiedenen Orten geladen werden, was zu dem netten "Objekttyp kann nicht in Zieltyp konvertiert werden" führt. Error.

UPDATE: Nach weiteren Tests scheint es, dass die ApplicationBase zwischen den beiden AppDomains unterschiedlich ist. Wenn sie übereinstimmen, funktioniert das obige Szenario. Wenn sie anders sind, tut es das nicht (obwohl ich bestätigt habe, dass die DLL mit Windbg aus dem selben Verzeichnis in beide AppDomains geladen wird). Wenn ich ShadowCopy = "true" in meinen beiden AppDomains einschalte, schlägt es fehl mit einer anderen Nachricht: "System.InvalidCastException: Object muss IConvertible implementieren".

UPDATE2: Weitere Lektüre führt zu der Annahme, dass es sich um Load Contexts handelt. Wenn Sie eine der "Von" -Methoden (Assembly.LoadFrom oder appDomain.CreateInstanceFromAndUnwrap) verwenden, wird die Assembly in einem der normalen Ladepfade (der ApplicationBase oder einem der Prüfpfade) gefunden und dann in den Standardpfad geladen Kontext laden Wenn die Assembly dort nicht gefunden wird, wird sie in den Load-From-Kontext geladen. Wenn also beide AppDomains übereinstimmende ApplicationBases haben, werden sie, obwohl wir eine "From" -Methode verwenden, beide in den Standard-Load-Kontext ihrer jeweiligen AppDomain geladen. Wenn sich die ApplicationBase jedoch unterscheiden, verfügt eine Anwendungsdomäne über die Assembly in ihrem Standardladungskontext, während die andere die Assembly in ihrem Load-From-Kontext hat.

+0

aber auch nach ** 'setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;' ** Problem stellt immer noch ... – Shelest

+0

@Shelest Versuchen Sie, Ihre ApplicationBase zu Path.GetDirectoryName ("Pfad der DLL-Datei") – Anshul

+0

@ RussellMcClure : Danke für deine Information - es hat mir geholfen, das für mich zu lösen. Mögen Sie sich meine Antwort anschauen und mir Ihre Gedanken über meine Lösung geben, denn es ist nicht wirklich elegant IMO ... – ChrFin

0

Dies ist ein Kommentar zu @RussellMcClure aber wie es zu komplex für einen Kommentar ist poste ich dies als eine Antwort:

ich in einer ASP-bin.NET-Anwendung und das Ausschalten Schatten-Kopie (das auch das Problem lösen würde) ist nicht wirklich eine Option, aber ich fand die folgende Lösung:

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; 
} 

Dies wird das Schatten-Kopie-Verzeichnis der wichtigsten app-Domain verwenden als Anwendungsbasis und fügen Sie alle schattenkopierten Assemblys dem privaten Pfad hinzu, wenn Schattenkopie aktiviert ist.

Wenn jemand eine bessere Möglichkeit hat dies zu tun, bitte sagen Sie mir.