Kompatiblität von DLLs

Alles zu Zusi-Performance, Frameraten, ruckelnden Bildern, Grafik, Treibern usw.
Antworten
Nachricht
Autor
Benutzeravatar
F. Schn.
Beiträge: 6700
Registriert: 24.10.2011 18:58:26

Kompatiblität von DLLs

#1 Beitrag von F. Schn. »

So, ausgehend vom Zusi-Treffen und mit Blick auf die aktuellen DLLs mal ein paar Gedanken zum Thema Kompatiblität von DLLs und Pointern auf Strukturen in DLLs.
Das Thema richtet sich primär an Carsten, der ja die Schnittstellen der DLLs festlegt.
  1. Der Name von Methoden muss eindeutig sein. Methoden dürfen nicht wie in vielen Programmiersprachen üblich überladen sein, d.h. sich nur in den Parametern unterscheiden.
  2. Die Signatur von Methoden sollte sich natürlich nicht ändern. Das heißt, es muss Reihenfolge, Anzahl und Datentyp der Parameter gleich sein, es darf sich aber z.B. der Wertebereich ändern.
    1. Dies sollte auch DLL-Übergreifend der Fall sein. Das heißt, es ist schlecht, wenn eine DLL eine Init mit einem Parameter hat, und die andere mit null Paramtern.
    2. Wenn das nicht beachtet wird, ist das bei x64 trotzdem kein Problem. Wenn eine Funktion einen Parameter mehr bekommt, können alte DLLs weiterhin mit dem neuen Zusi laufen und neue DLLs laufen mit dem alten Zusi, sofern sie nicht auf den Paramter zugreifen. Wenn sie doch zugreifen, bekommen sie bei Zahlen Müll, bei Pointern stürzen sie ab.
    3. Bei x86 (32 bit) ist bei __stdcall aber (frei nach Alwin) Tag des Kotzenden Einhorns und der Programmierer hat den Hauptpreis gewonnen und muss die Größe des zu bereinigenden Stacks von Hand in Assembler festlegen. Zusi könnte da zwar vermutlich auch selbst aufpassen, aber vermutlich wird auch das Eingriffe in Assembler erfordern. Also einfachste Lösung: 32bit aufgeben, oder einfach unterschiedliche Namen verwenden.
    4. Der Standard-Name für Funktionen, die geänderte Parameter bekommen haben, lautet unter Windows "Blub" -> "BlubEx" (Extension) -> "BlubEx2" -> ...
    5. Wenn Zusi die alten DLLs unterstützen will, kann Zusi prüfen, ob eine "BlubEx" existiert und wenn nicht die "Blub" aufrufen.
  3. Übergabe von Strukturen von Zusi an eine DLL:
    1. Grundsätzlich gilt: Die Reihenfolge der Felder in einer Struktur darf nicht geändert werden. Das heißt neue Felder müssen immer ganz unten dazu geschrieben werden.
    2. Werden zusätzliche Felder in die Struktur eingefügt, muss man nicht zwingend eine neue Methode aufmachen. Man muss aber dafür ein paar Vorarbeiten getroffen haben:
    3. Option 1: Man macht einfach ein paar "Reserviert: Immer 0"-Felder ans Ende der Struktur (oder auch zwischen rein). Die DLL greift auf diesen Wert dann einfach nicht zu, man kann den Datentyp dann nachher weitgehend beliebig ändern, solange er gleich groß ist.
    4. Option 2: Man macht als erstes Feld in der Structure ein Feld, in dem die Größe der Struktur angegeben ist. Also sitz_t size = sizeof(Structure); Einige Windows-Messages haben so ein Vorgehen. Die DLL kann dann anhand dieses Größen-Feldes unterscheiden, ob es auf die neu hinzugekommenen Felder zugreifen darf oder nicht. Alte DLLs würden weiterhin funktionieren, neue könnten bewusst feststellen, was sie gerade machen wollen. Edit: Zudem müssen die Strukturen als var übergeben werden, ansonsten geht die 32-Bit-Verison nicht.
    5. Option 3: Man macht gar keine Vorbereitungen. Wenn man die Reihenfolge der Felder in der Struktur nicht ändert und neue Felder immer ganz ans Ende schreibt, funktionieren unter allen Prozessor-Architekturen die alten DLLs weiterhin. Neue DLLs sind dann unproblematisch, wenn sie nicht auf die neuen Felder zugreifen. Wenn sie doch auf die neuen Felder zugreifen, aber mit dem alten Zusi betrieben werden, stürzt Zusi ab. Das wäre aber in meinen Augen trotzdem ein verkraftbarer Sonderfall. Edit: Zudem müssen die Strukturen als var übergeben werden, ansonsten geht die 32-Bit-Verison nicht.
    6. Im Notfall kann man dann noch eine neue Methode aufmachen, aber in meinen Augen ist das nicht nötig. Ich denke, Option 2 wäre die einfachste.
Zuletzt geändert von F. Schn. am 17.09.2023 01:12:16, insgesamt 1-mal geändert.
Diese Signatur möchte folgendes bekannter machen: ZusiWiki · ZusiSK: Streckenprojekte · YouTube: Objektbau für Zusi · euirc: Zusi-Chat

Benutzeravatar
F. Schn.
Beiträge: 6700
Registriert: 24.10.2011 18:58:26

Re: Kompatiblität von DLLs

#2 Beitrag von F. Schn. »

So, noch ein paar Gedanken zur procedure Berechnung(Instanz:Pointer; dt:double; AntriebsRenderModus:TAntriebsRenderModus; Mehrfachtraktionsdaten:TMehrfachtraktionsdaten); stdcall;
bzw. ein paar Ergänzungen bzw. eigentlich sogar Korrekturen zu oben. Konkret geht es um den TMehrfachtraktionsdaten-Parameter. TAntriebsRenderModus ist eine enum, aber TMehrfachtraktionsdaten ist eine Struktur.

4. Übergabe von Strukturen von Zusi an eine DLL ohne das Stichwort "var":
In der 64-Bit-Version wird die Angabe stdcall ignoriert und stattdessen die x64 calling convention angewendet. Strukturen bis zur Größe von 64 bit werden hier ganz normal übergeben. Bei größeren Strukturen wird stattdessen ein Pointer auf die Struktur übergeben. Dies führt effektiv dazu, dass TMehrfachtraktionsdaten wie eine konstante Referenz, also quasi wie ein TPMehrfachtraktionsdaten oder wie ein var TMehrfachtraktionsdaten übergeben wird. Wenn ich also die Mehrfachtraktonsdaten nicht benötige, und besonders faul bin, kann ich auch darauf verzichten, die Klasse TMehrfachtraktionsdaten nach C zu konvertieren und stattdessen einfach void* schreiben.

Für die Zukunft heißt das: Künftige Änderungen der TMehrfachtraktionsdaten sind in 64 bit nicht so wichtig. Stattdessen gilt, was ich oben zu 3. Übergabe von Strukturen von Zusi an eine DLL geschrieben habe. Was im Prinzip relativ Handzahm ist.

Anders natürlich bei 32bit __stdcall. Für var Prot:TProtokollFst gilt zwar das gleiche. Aber TMehrfachtraktionsdaten (ohne var) wird auf 32-Bit-Vielfaches aufgeblasen und auf den Stack gelegt. Mit dem Ergebnis, das sich unter 32 bit effektiv die Signatur ändert, wenn man Änderungen an der Struktur macht.

Und das heißt für die Zukunft: Es gilt das, was ich unter 2. Die Signatur von Methoden geschrieben habe. Das heißt, es gibt jetzt sogar für 32 bit einen schwer vorhersehbaren Fall, bei dem Fehler in der 64-bit-Version unentdeckt bleiben können, in der 32-bit-Version aber schweren Schaden anrichten können. Noch dazu wird der Fehler nicht bemerkt, wenn die Prozedur nicht aufgerufen werden muss, z.B. weil bestimmte Prozeduren nicht aufgerufen werden, wenn der Spieler den Zug nicht fährt. (Wenn sie aufgerufen wird, Crasht es aber in diesem Fall zuverlässig.)

Ich würde daher empfehlen, bei DLLs Strukturen grundsätzlich mit dem var-Schlüsselwort (oder const-var-Schlüsselwörtern, wenn es die in Delphi gibt) zu übergeben.

5. Enums:
Enums und bools werden in vielen Fällen auf 32 bit aufgeblasen. Aber in dem aufgeblasenen Rest kann Müll stehen. Im Endeffekt ist das nur bei enums relevant: Dort muss man aufpassen, wenn man die Grenze von 256 Einträgen überschreitet. Ansonsten ist das Hinzufügen von Enum-Einträgen aber erst mal unproblematisch. C-Nutzer müssen halt von byte erben. (-> enum class TAntriebsRenderModus : byte)
Diese Signatur möchte folgendes bekannter machen: ZusiWiki · ZusiSK: Streckenprojekte · YouTube: Objektbau für Zusi · euirc: Zusi-Chat

Antworten