Forum: PC-Programmierung Zwei DLLs in verschiedenen Versionen einbinden C#


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Kafulluk C. (kafulluk)


Lesenswert?

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?

von Cyblord -. (cyblord)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Der Freundliche Gast X. (Firma: mc.net) (friendly_offtopic)


Lesenswert?

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
von Kafulluk C. (kafulluk)


Lesenswert?

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
von Der Freundliche Gast X. (Firma: mc.net) (friendly_offtopic)


Lesenswert?

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
von Udo K. (udok)


Lesenswert?

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

von Kafulluk C. (kafulluk)


Lesenswert?

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
von Heinz B. (Firma: Privat) (hbrill)


Lesenswert?

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
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Bertram S. (bschall)


Lesenswert?

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
Noch kein Account? Hier anmelden.