Hallo, ich arbeite an einem Projekt das Komponenten von Drittherstellern verwendet. Eine Komponente liegt nun in zwei Versionen vor und ich muss leider beide unterstützen. In Version 2 wurde eine zusätzliche Basisklasse eingeführt, die in Version 1 noch nicht vorhanden war. Es gibt also 2 DLLs: Komp.dll (in Version 1.0) Komp.dll (in Version 2.0) Im Programm würde ich gerne zur Laufzeit je nach Auswahl Version 1 oder 2 laden. Ich habe zwei Projekte in der Solution aufgemacht und jeweils auf die verschiedenen DLLs referenziert. Bei beiden DLLs habe ich die Option "Lokale Kopie" auf "False" gesetzt, da ich sie zur Laufzeit aus einem anderen Installationsordner laden möchte (Assembly.LoadFrom(path)). In der Main Methode habe ich eine Methode Resolver auf das Event AssemblyResolve registriert. Resolver wird somit ausgeführt, wenn die Auflösung einer Referenz zur Laufzeit notwending ist. Ich stehe vor folgendem Problem: Wenn ich Version 2 auswähle, wird die zugehörige DLL ordnungsgemäß gefunden und erfolgreich geladen. Wähle ich jedoch Version 1 aus, erhalte ich eine Ausnahme mit der Meldung: System.IO.FileNotFoundException Datei oder Assembly konnte nicht geladen werden. Das System konnte die angegebene Datei nicht finden Interessanterweise funktioniert das Laden von Version 1 problemlos, sobald ich den Code für Version 2 aus dem Projekt ausschließe. Die DLLs befinden sich in den jeweils vorgesehenen Verzeichnissen – dies überprüfe ich zusätzlich durch einen gezielten Aufruf von File.Exists(), um sicherzustellen, dass die Dateien tatsächlich vorhanden sind. Was mache ich falsch?
Das lässt sich letztendlich auf die Frage reduzieren ob man zwei DLLs in unterschiedlichen Namespaces betreiben kann. Denn die Namen der Objekte und Methoden und Felder sind ja nun mal identisch. Da führt kein Weg dran vorbei. Bleibt also nur der Namespace um zwischen zwei fast identischen DLLs zu unterscheiden. Den führt aber auch die DLL ein. Deshalb spielt es auch keine Rolle ob die Projekte unterschiedlich sind. Ich sehe da eher schwarz.
Du könntest den Teil deines Projekts, welcher von der DLL-Version abhängt, in 2 separate DLLs auslagern - eine für Version 1, eine für Version 2. Die DLLs werden gegen die jeweilige DLL der Komponente gebaut. Das Interface deiner beiden DLLs sollte identisch sein. Gewissermaßen also 2 Wrapper-DLLs für die beiden Komponentenversionen. Du lädst dann dynamisch die richtige Version deiner DLL.
Wahrscheinlich heißen die unterschiedlichen DLLs gleich und auch die Funktionen darin. Gute Voraussetzungen, um das OS maximal zu verwirren. Wer macht denn so einen Unsinn?
Peter D. schrieb: > Wer macht denn so einen Unsinn? Ach, das ist doch harmlos, sowas und noch viel schlimmeres passiert ständig. Es gibt so unglaublich viele miserable proprietäre Module/APIs bei denen man sich fragt wie die Ersteller auf "diese Idee" gekommen sind. Selbst bei z.B. OpenSSL ist das mit den Versionen ein Chaos. Sinnlos sich darüber aufzuregen, schlecht für den Blutdruck. Man muss es "nur irgendwie einbinden".
Auf das Problem stößt man immer wieder mal, z.B. auch wenn man mehrere Libraries einbindet die wiederum transitive Abhängigkeiten zum gleichen Logging-Framework mit unterschiedlichen Versionen haben. Es gibt je nach Situation unterschiedliche Möglichkeiten das Problem zu lösen. Eine brachiale Holzhammermethode die wir auch schon angewendet haben ist eine der beiden gleichen DLLs mit der eigenen zu verschmelzen und deren benutzte Typen zu renamen dann zu internalisieren, dafür gibt es Tools. Dann gäbe es noch binding-redirects, friend/extern declarations, GAC, ... Es ist jedenfalls kein seltenes Problem und es gibt wie gesagt verschiedene Lösungen je nach Situation.
:
Bearbeitet durch User
Der Freundliche Gast X. schrieb: > Auf das Problem stößt man immer wieder mal, z.B. auch wenn man > mehrere > Libraries einbindet die wiederum transitive Abhängigkeiten zum gleichen > Logging-Framework mit unterschiedlichen Versionen haben. > Es gibt je nach Situation unterschiedliche Möglichkeiten das Problem zu > lösen. Eine brachiale Holzhammermethode die wir auch schon angewendet > haben ist eine der beiden gleichen DLLs mit der eigenen zu verschmelzen > und deren benutzte Typen zu renamen dann zu internalisieren, dafür gibt > es Tools. > Dann gäbe es noch binding-redirects, friend/extern declarations, GAC, > ... > Es ist jedenfalls kein seltenes Problem und es gibt wie gesagt > verschiedene Lösungen je nach Situation. In meinem Ansatz habe ich die Lösung so strukturiert, dass sie aus drei Projekten besteht: zwei Versionsprojekten und einem gemeinsamen Interfaceprojekt. Die Benutzer trifft beim Start eine einmalige Auswahl, ob man mit Version 1 oder Version 2 arbeiten soll. Die Anwendung greift dann entsprechend auf den Code der ausgewählten Version zu. Ein Wechsel der Version ist nur durch einen Neustart der Anwendung möglich. Die Projektstruktur sieht wie folgt aus: ProjektVersion1 (Klassenbibliothek) Verweise DLL Version 1 (Lokale Kopie=false) CodeDerMitDerDllV1Umgeht.cs ... ProjektVersion2 (Klassenbibliothek) Verweise DLL Version 2 (Lokale Kopie=false) CodeDerMitDerDllV2Umgeht.cs ... ProjektSchnittstellen (Schnittstellen) Applikation Verweise ProjektVersion1 (Lokale Kopie=true) ProjektVersion2 (Lokale Kopie=true) ProjektSchnittstellen (Lokale Kopie=true) Resolver.cs ... Projekt 1 verwendet Methoden aus Version 1, während Projekt 2 auf Methoden aus Version 2 zurückgreift. Die Schnittstellen kennen die zugrunde liegenden DLLs nicht. Die Anwendung greift über die Schnittstellen auf den Code von V1 oder V2. Wird Version 2 ausgewählt, funktioniert alles reibungslos. Wird Version 1 ausgewählt, tritt beim Laden der Assembly (z. B. var asm = Assembly.LoadFrom(path)) eine Ausnahme auf. Interessanterweise lässt sich Version 1 fehlerfrei laden und nutzen, wenn Version 2 vollständig aus dem Projekt ausgeschlossen wird. Auszug aus dem Exception: === Pre-bind state information === LOG: Where-ref bind. Location = C:\Program Files\ hier der path \Name.dll LOG: Appbase = file:///C:/Users/hier der path/bin/Debug/ LOG: Initial PrivatePath = NULL Calling assembly : (Unknown). === LOG: This bind starts in LoadFrom load context. WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load(). LOG: Using application configuration file: C:\Users\hier der path\bin\Debug\Application.exe.Config LOG: Using host configuration file: LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config. LOG: The same bind was seen before, and was failed with hr = 0x80070002. Habt ihr einen Vorschlag wie man das lösen könnte?
:
Bearbeitet durch User
Als einfachster Workaround (falls die Auswahl schon am Start stattfindet): Deine Applikation zweimal bauen einmal mit v1 einmal mit v2 und den Auswahldialog als Starter behandeln, der je nach Auswahl appv1 oder appv2 startet.
:
Bearbeitet durch User
Cyblord -. schrieb: > Das lässt sich letztendlich auf die Frage reduzieren ob man zwei DLLs in > unterschiedlichen Namespaces betreiben kann. Denn die Namen der Objekte > und Methoden und Felder sind ja nun mal identisch. Da führt kein Weg > dran vorbei. Dynamisches Laden mehrerer DLLs mit unterschiedlichen Versionen ist in C/C++ kein Problem. Siehe Hilfe zu LoadModule() und GetProcAddress(). Ich gehe davon aus, dass das auch in C# problemlos möglich ist. Kurze Google Suche: https://www.experts-exchange.com/questions/23865024/Use-unmanaged-C-DLL-in-C.html Gruss Udo
Der Freundliche Gast X. schrieb: > Als einfachster Workaround (falls die Auswahl schon am Start > stattfindet): > > Deine Applikation zweimal bauen einmal mit v1 einmal mit v2 und den > Auswahldialog als Starter behandeln, der je nach Auswahl appv1 oder > appv2 startet. Ohne den App-Code zu duplizieren? Die Erstellung der Setup-Datei wird dann aufwendiger, da zusätzliche manuelle Schritte erforderlich sind. Die Auswahl zwischen den Versionen erfolgt aber nicht direkt beim Start der Anwendung, sondern erst, wenn ein bestimmter Prozess gestartet wird. Danach wird AssemblyResolve ausgelöst und im AssemblyResolve-Handler wird versucht die entsprechende DLL zu laden. Könnte mein oben beschriebener Ansatz dennoch funktionieren, wenn dieser ergänzt wird? Ich hatte das zunächst über Extern-Alias implementiert, jedoch führte diese Vorgehensweise letztlich zum gleichen Ergebnis wie meine aktuelle Lösung: https://learn.microsoft.com/de-de/dotnet/csharp/language-reference/keywords/extern-alias Oder habe ich das möglicherweise falsch verstanden?
:
Bearbeitet durch User
Warum nimmt er nicht gleich die Version 2 ? Dort ist doch nur eine zusätzliche Basisklasse drin. Ob man diese dann benutzt, hängt ja vom Code ab. Wenn nicht, wird diese zus. Basisklasse halt nicht initialisiert und wird im Speicher mitgeschleppt. Die paar Bytes mehr spielen ja keine so große Rolle. Ich arbeite ja auch öfter mit DLLs, wo ich längst nicht alle Funtkionen darin brauche (z.b zus. Grafikfunktionen o. ä.). Insofern verstehe ich den ganzen Sinn nicht, wenn halt sonst alles identisch sein soll. Oder gibt es doch noch mehr Unterschiede ?
:
Bearbeitet durch User
Heinz B. schrieb: > Insofern verstehe ich den ganzen Sinn nicht, wenn halt sonst alles > identisch sein soll. Sicherlich ist die DLL ein Interface zu irgendeinem externen System, Protokoll, Datenformat, und Version 2 ist nicht abwärtskompatibel, bzw. die eigene Software muss immer die auf dem PC installierte DLL nutzen, egal welche Version es ist.
Ohne code wird es schwierig... Dennoch: als ersten Ansatz vielleicht die DLLs in einer neuen AppDomain zu laden und dann via DoCallBack "reinrufen" Nachtrag: AssemblyLoadContext unter netCore
:
Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.