Nach dem letzten Blog meines gleichgesinnten Kollegens hat mich das Thema P/Invoke nicht mehr los gelassen. Ich ging nur mit einer etwas anderen Einstellung in das Thema. Mein Programmche soll nicht die File Redirection von %systemroot%\system32 nutzen. Nein ich will keine Forwarder-Library in das System32-Directory kopieren. Zustimmung erhält aber trotzdem das Mediator.Prinzip. Dieser Mediator bestimmt welche native DLL geladen wird. Im Fall von 64Bit Windows wäre das die 64Bit-Dll, im Fall von 32Bit Windows die 32Bit-Dll und im Falle von Windows CE und dem Compact Framework eine andere.
Mein Ziel ist aber die gesamte Distribution meiner Software in %ProgramFiles%\MySoftware zu handeln und eine foo.dll für 32Bit Windows, eine foo64.dll für 64Bit Windows und eine fooCe.dll für Windows CE zu halten.
Versuch Nummer 1:
Bendenkt man das Attribute in .NET nur Klasse abgeleitet von System.Attribute sind, liegt die Idee nah das DllImportAttribute erneut abzuleiten und im Property-Handling für den DllName die Logik für die Namens-Nomenklatur zu Implementieren. Leider macht das kleine aber wichtige Schlüsselwort sealed einen Strich duch die Rechnung. Damit sorgt das .NET Framework das keine weitere Ableitung von dieser Klasse möglich ist. Der nächste Versuch war irgendeine Art Logik hinter den DllName zu hängen. Leider unterstützt auch hier das .NET Framework nur konstante Parameter-Werte in Attributen. Also auf zu neuen Ufern.
Versuch Nummer 2:
Verfolgt man das Mediator-Konzept weiter, will aber keine eigene Forwarder-Dll schreiben, ist vielleicht folgendes Konzept hilfreich. Für jede unterstützte Plattform wird eine DllImportHelper-Klasse implementiert. Diese kapselt lediglich das Marshaling und die DllImport-Anweisung. Um den Zugriff auf die richtige Helper-Klasse zu realisierien wird eine Schnittstelle zur Verfügung gestellt. DieseDllImport-Klasse orchestriert den Zugriff auf die korrekte Helper-Klasse. Je nach aktueller Plattform wird die entsprechende Methode aus der korrekter DllImport-Helper-Klasse aufgerufen.
Vorteile:
- Das Mediator-Konzept wurde beibehalten und das Assembly kann einfach um weitere Plattformen erweitert werden. Baut man das Konzept aus, so ist eine Erweiterung für neue Platformen in reinem Managed-Code ohne Veränderung des aufrufenden Codes möglich.
- Die Verwaltung einer extra Forwarder-Dll entfällt.
- Die Applikation muss keine Libraries in %systemroot%\system32 ablegen.
Nachteil:
- Die Bestimmung der aktuellen Platform kostet extra Zeit und verlangsamt den Sprung in den native Code nochmals.
- Es muss für jede Platform eine extra Klasse im Assembly gehalten werden aber kein zweites Assembly.
Ein einfaches Beispiel ist hier hinterlegt. Dieses Beispiel soll nur illustrieren wie das prinzipielle Vorgehen ist und wurde nicht für den produktiven Einsatz vorgesehen.
64Bit Runtime Check:
Wie findet der geneigte Entwickler in C# heraus ob sich der Prozess gerade als 64Bit bzw. 32Bit-Prozess läuft. Leider ist festzustellen das Microsoft in seinem Environment-Objekt keine passable Lösung für das Problem vorrätig hat. Folgende Lösung stammt aus diesem Blog und hier findet man wie es nicht gemacht werden sollte. Unter C# ist ein int-Datentyp immer 32Bit lang, egal ob 64Bit-Prozess der 32Bit-Prozess aber der IntPtr ist je nach Prozess-Art 32Bit oder 64Bit lang.
bool bIs64Bit = (sizeof(IntPtr) == 8 )