Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Flexible(re) Ladeprozedur (https://www.delphipraxis.net/88564-flexible-re-ladeprozedur.html)

Master-of-Magic 17. Mär 2007 10:07


Flexible(re) Ladeprozedur
 
Erstmal hoff ich, dass ich die richtige Spart erwischt hab. Es gibt ja kein extra Unterforum für Dateien & Co - falls doch, dann sponsort mir eine Brille :mrgreen:

Folgendes: Ich habe ein Tool das seit einiger Zeit entwickelt wird und wo ab und zu neue Versionen herauskommen (wer hätte es gedacht *g*). Da sich dadurch die Speicherstruktur ändert, muss ich ältere Dateien abwärts kompatibel einlesen können. Meine Datei enthält deshalb einen Integer für die Version und dann mein Array of Record.

Eingelesen hab ich es bisher mit 'nem Filestream und einer case Abfrage nach dem Motto:

Delphi-Quellcode:
case version of
1: Einlesen und an aktuellen Record anpassen
2: Einlesen und an aktuellen Record anpassen
3: Einlesen und an aktuellen Record anpassen
4: Direkt Einlesen, da aktuelle Version
Das Prinzip sollte klar sein. Aber es hat einen großen Nachteil: Für jede neue Version muss ich alle cases anpassen, wenn sich die Speicherstruktur verändert hat.


Daher hab ich mir momentan folgende Variante überlegt:

1. Datei in Filestream (fs) einlesen und Version auslesen
2. fs in Memorystream (ms) kopieren
3.
Delphi-Quellcode:
if version=1 then
begin
  ms an Record von Version 2 (!) anpassen und in Temp-Memorystream (temp) kopieren
  temp wieder nach ms zurückkopieren
  version auf 2 setzen
end;
if version=2 then
begin
  ms an Record von Version 3 (!) anpassen und in Temp-Memorystream (temp) kopieren
  temp wieder nach ms zurückkopieren
  version auf 3 setzen
end;
if version=3 then
begin
  ms an Record von Version 4 (!) anpassen und in Temp-Memorystream (temp) kopieren
  temp wieder nach ms zurückkopieren
  version auf 4 setzen
end;
[...usw...]
if version=4 then
begin
  ms direkt in Speicher einlesen
end;
Ich hoffe der Pseudo-Code ist verständlich und vermittelt meine Idee. Mit der Variante muss ich immer nur einen neuen If-Block für eine neue Speicherstruktur einbauen, was das Handling vereinfacht. Ältere Versionen steigen entsprechend früher in den Code ein und werden angepasst.
Allerdings muss ich mich so mit drei Streams herumschlagen die die Datei enthalten -> nicht gerade Ressourcenfreundlich.

Hat mir da jemand Tipps/Verbesserungsvorschläge? Die Datei hat zwar nur 50K, aber hier gehts ums Prinzip ;)
Oder gibts einen völlig anderen Weg?

Der_Unwissende 17. Mär 2007 10:55

Re: Flexible(re) Ladeprozedur
 
Zitat:

Zitat von Master-of-Magic
Erstmal hoff ich, dass ich die richtige Spart erwischt hab. Es gibt ja kein extra Unterforum für Dateien & Co - falls doch, dann sponsort mir eine Brille :mrgreen:

Hi,
ohne dass ich jetzt irgendeinen Einfluss auf die Einordnung durch die Moderatoren habe (außer vielleicht ich kann mich glaubhaft über deine Einordnung beschweren), ich würde nicht sagen dass es so direkt etwas mit dem Windows API zu tun hat (oder übersehe ich die API Zugriffe?). Fände ObjectPascal/Delphi-Language oder sogar sonstige Fragen passender.

Aber ist ja auch egal!

Zitat:

Zitat von Master-of-Magic
Daher hab ich mir momentan folgende Variante überlegt:

1. Datei in Filestream (fs) einlesen und Version auslesen
2. fs in Memorystream (ms) kopieren

Ok, kannst Du diesen Teil denn schon begründen? Also an sich ist die Idee sicherlich, dass Du schneller arbeiten würdest, wenn Du statt einem FileStream einen MemoryStream verwendest, ist imho nicht so. Schon der FileStream scheint automatisch einen Puffer für das Lesen/Schreiben zu verwenden und ist mit dem deutlich schneller unterwegs als dein MemoryStream (der immer komplett im Speicher landet). Das Problem des MemoryStream ist, dass Du mit dem halt einen sehr sehr großen Bereich im Speicher belegen kannst (bei 50 KByte natürlich nicht, aber bei wirklich großen Dateien). Somit landet ein Teil von ihm schnell im Virtuellen Speicher und/oder verdrängt andere Daten sinnlos aus dem Hauptspeicher.
Hier solltest Du schauen, ob Du wirklich Geschwindigkeit durch die Verwendung eines MemoryStreams gewinnst. Schon das Umkopieren dürfte allerdings mehr Zeit kosten. Kann natürlich auch sein, dass Du es aus einem anderen Grund machst, aber eigentlich sollte alles auch mit einem FileStream möglich sein.

Zitat:

Zitat von Master-of-Magic
3.
Delphi-Quellcode:
if version=1 then
begin
  ms an Record von Version 2 (!) anpassen und in Temp-Memorystream (temp) kopieren
  temp wieder nach ms zurückkopieren
  version auf 2 setzen
end;
if version=2 then
begin
  ms an Record von Version 3 (!) anpassen und in Temp-Memorystream (temp) kopieren
  temp wieder nach ms zurückkopieren
  version auf 3 setzen
end;
if version=3 then
begin
  ms an Record von Version 4 (!) anpassen und in Temp-Memorystream (temp) kopieren
  temp wieder nach ms zurückkopieren
  version auf 4 setzen
end;
[...usw...]
if version=4 then
begin
  ms direkt in Speicher einlesen
end;

Hm, auch hier würde ich denken, dass das umkopieren recht viel Zeit kostet. An sich ist die Frage, wie deine Daten überhaupt gespeichert sind ganz Interessant. Liegen die Records denn direkt hintereinander in der Datei? Und weißt Du wieviele es sind?
Wenn dies der Fall ist, dann kannst Du sehr effezient einfach den ganzen Block aller Datensätze in ein entsprechendes Array einlesen. Je nach Versionsnummer erstellst Du einfach ein dyn. Array der entsprechenden Größe und des entsprechenden Typs.

Die restliche Arbeit kannst Du dann einfach auf einem solchen Array ausführen. Deine Idee immer die Anpassung an die nächste Version vorzunehmen solltest Du natürlich genauso beibehalten. Der Unterschied liegt dann nur darin, dass Du diese Datensätze nicht extra aus einem Stream extrahierst und wieder dorthin zurückschreibst, sondern gleich mit einem festen Datentypen arbeitest. Das sollte die Arbeit deutlich vereinfachen.

An sich muss jede Version dann zwei Methoden anbieten:
  1. Auslesen eines TypX-Arrays aus einem Stream
  2. Konvertieren der Vorgängerversion (X-1) in die eigene (vorher weißt Du ja nicht was nötig ist).

Und als Pseudocode:
Delphi-Quellcode:
type
  TTyp1 = record
    ...
  end;
  TTyp1Array = Array of TTyp1;

  TTyp2 = record
    ...
  end;
  TTyp2Array = Array of TTyp2;

  TTyp3 = record
    ...
  end;
  TTyp3Array = Array of TTyp3;

  TAktuellerTyp = TTyp3;
  TAktuellerTypArray = Array of TAktuellerTyp;

function loadFromStream(const Stream: TStream): TAktuellerTypArray;
var versionsNummer: Integer;
begin
  ...
  case versionsNummer of
    1: result := convert(readTyp1(Stream));
    2: result := convert(readTyp2(Stream));
    3: result := convert(readTyp3(Stream));
  end;

end;

function readTyp1(const Stream: TStream): TTyp1Array;
begin
  // ist klar!
end;

function readTyp2(const Stream: TStream): TTyp2Array;
begin
  // ist klar!
end;

//...

function convert(var typ1Array: TTyp1Array): TAktuellerTypArray;
var typ2Array: TTyp2Array;
begin
  // Umwandlung von typ1Array in ein Typ2 Array
  // ....
 
  // typ1Array wird nicht mehr benötigt, Speicher frei geben!
  setLength(typ1Array, 0);
  finalize(typ1Array);
 
  result := convert(typ2Array);
end;

// ....

function convert(const typ3Array: TTyp3Array): TAktuellerTypArray;
begin
  result := typ3Array;
end;
So ungefähr würde ich es Dir vorschlagen. Die Idee ist hoffentlich auch hier klar. Du nimmst einfach einen Alias für den aktuellen Datentypen und gibt ein Array von diesem Typen als Ergebnis des Ladens zurück.
Für die aktuelle Version muss also nur das übergebene Array zurückgegeben werden. Kommt eine neue Version hinzu, erstellst Du den entsprechenden neuen Datentypen und änderst den Alias TAktuellerTyp entsprechend ab. Zudem musst Du convert für diesen Typen überladen und das convert der Vorgängerversion anpassen. Das Laden gehört dann natürlich auch noch in die case-Anweisung, aber an sich halten sich (hoffentlich) die Änderungen in Grenzen.

Wie gesagt, der eigentliche Unterschied zu deinem Ansatz liegt hier nur darin, dass Du gleich mit Datentypen arbeitest und nicht den Umweg über zwei Streams gehst.

Gruß Der Unwissende

Jelly 17. Mär 2007 12:34

Re: Flexible(re) Ladeprozedur
 
Warum hat Gott die objektorientierte Programmierung erfunden.

Erstell Dir zu jeder Version eine Klasse, die sich ums Kovertieren kümmert. Willst Du ganz generisch halten, und dir die if-Abfragen (bzw. deine Case Strukur was genau das gleiche ist nur in grün), so kannst Du mit Metaklassen arbeiten. Ich hatte diese Problematik auch mal erfragt, und in diesem Thread wurd ziemlich gut erklärt, wie du sowas handeln kannst.

Im Grunde wird aufgrund der Version, die als Integer irgendwie vorliegen könnte, die entsprechende richtige Klasse instanziert.

Master-of-Magic 17. Mär 2007 13:51

Re: Flexible(re) Ladeprozedur
 
Zitat:

Zitat von Der_Unwissende
Zitat:

Zitat von Master-of-Magic
Daher hab ich mir momentan folgende Variante überlegt:

1. Datei in Filestream (fs) einlesen und Version auslesen
2. fs in Memorystream (ms) kopieren

Ok, kannst Du diesen Teil denn schon begründen?

Ja, ich hab mir davon einen Geschwindigkeitsvorteil erhofft, wenn im Ram gearbeitet wird, anstatt die HD zu belasten. Im Prinzip ist der Streamtyp egal.

Zitat:

An sich ist die Frage, wie deine Daten überhaupt gespeichert sind ganz Interessant. Liegen die Records denn direkt hintereinander in der Datei? Und weißt Du wieviele es sind?
Ja und Ja. Allerdings ist das Ganze etwas kompleer als nur einfach ein paar Records. Die Struktur sieht grob so aus:
Delphi-Quellcode:
type
  TPlaneten = packed Record
    [statische Arrays, Strings und Daten]
  end;
  TAccount = packed Record
    Planet: array[0..12] of TPlaneten;
    [statische Arrays, Strings und Daten]
  end;
var
  Account:Array[0..12] of TAccount;
Gespeichert wird einfach das Account-Array. Ändern tun sich in den Versionen meist die Anzahl der Account und die Inhalte des TPlaneten-Records.

Zitat:

Du nimmst einfach einen Alias für den aktuellen Datentypen und gibt ein Array von diesem Typen als Ergebnis des Ladens zurück.
Für die aktuelle Version muss also nur das übergebene Array zurückgegeben werden. Kommt eine neue Version hinzu, erstellst Du den entsprechenden neuen Datentypen und änderst den Alias TAktuellerTyp entsprechend ab. Zudem musst Du convert für diesen Typen überladen und das convert der Vorgängerversion anpassen. Das Laden gehört dann natürlich auch noch in die case-Anweisung, aber an sich halten sich (hoffentlich) die Änderungen in Grenzen.
Ich seh da aber ein Problem: Ich muss für jede Version das komplette Record erstellen. Würde das nicht heißen, ich hab für jede Version den Speicher reserviert? Mir scheint, dass das noch aufwändiger und Speicher fressender ist, als meine bisherige Methode ...

@Jelly
Zitat:

Warum hat Gott die objektorientierte Programmierung erfunden.
Ach Gott war das? Ich dachte, das wäre eine Nebenwirkung der Erfindung von C++ & Co. gewesen ... :gruebel:

Zitat:

Erstell Dir zu jeder Version eine Klasse, die sich ums Kovertieren kümmert. Willst Du ganz generisch halten, und dir die if-Abfragen (bzw. deine Case Strukur was genau das gleiche ist nur in grün), so kannst Du mit Metaklassen arbeiten. Ich hatte diese Problematik auch mal erfragt, und in diesem Thread wurd ziemlich gut erklärt, wie du sowas handeln kannst.
Da muss ich leider sagen, dass ich noch nie bewusst mit Klassen gearbeitet hab und daher auch keine Ahnung davon hab. Hast du mir da ein kleines Tutorial bzw. kannst mir ein Beispiel dazu geben?
Weil bisher klingt das auch so, als ob ich für jede Version das Array of Record erstellen müsste ... und das scheint mir mehr Aufwand, als mein bisheriger Weg.

Jelly 17. Mär 2007 14:13

Re: Flexible(re) Ladeprozedur
 
Kuck mal auf Luckie's Homepage, da gibts was zu Klassen.
Prinzipiell wirst du gleiche Sachen in einer Basisklasse definieren, und versionsabhängige Sachen in davon abgeleiteten Klassen. Etwa so, in sehr knapper Form:

Delphi-Quellcode:
type
  TBasis = class
  public
      Planetname : string ;

      // ...

      constructor Create (Filename : string) ;

      procedure Load ; virtual ;

  end;

  TPluto = class (TBasis)
  public
      procedure Load ; override ;
  end;
  TVenus = class (TBasis)
  public
      procedure Load ; override ;
  end;
Nutzen geht dann so:

Delphi-Quellcode:
var
  Pluto, Venus : TBasis ;
begin
     Pluto := TPluto.Create ('file.txt') ;
     Venus := TVenus.Create ('file.txt') ;
     Pluto.Load ;
     Venus.Load ;
end.
Dadurch wird dann jeweils die korrekte Load Prozedur aufgerufen.

Master-of-Magic 17. Mär 2007 15:32

Re: Flexible(re) Ladeprozedur
 
Mit den Planetennamen in deinem Beispiel meintest du die verschiedenen Versionen meiner Datei, oder? Weil das ist etwas unglücklich gewählt ;) - im Prinzip verändern sich ja nicht nur die Planeten, sonder auch mal die Accounts bzw. es kommen neue Accounts (+Planeten) dazu.

Wenn ich dein Beispiel (und Luckies Tutorial) recht verstanden habe, wäre ich mit der Lösung zwar elegant, aber wieder am Anfang. Weil die einzelnen Konvertierungen werden ja nicht mehr nacheinander, sondern wieder 'auf einmal' ausgeführt. Und somit müsste ich bei einer neuen Version wieder alle .Load Prozeduren ändern, damit sie an das aktuelle record angepasst werden. :(

Jelly 17. Mär 2007 16:35

Re: Flexible(re) Ladeprozedur
 
Natürlich musst du die load Prozedur bei einer neuen Version neu schreiben. Wie soll das auch anders gehen :gruebel:

Master-of-Magic 17. Mär 2007 17:01

Re: Flexible(re) Ladeprozedur
 
Ich glaub du hast mich falsch verstanden. Sicher muss ich die Load Prozedur erweitern. Aber in meiner obigen Variante muss ich nur die If-Abfrage und das Einlesen der neuen Version hinzufügen.

Bei meiner alten und bei deiner Variante - sofern ich dich recht verstanden habe - müsste ich alle Load-Procedures für jede Version updaten... verstehst du mein Problem? :stupid:

Jelly 17. Mär 2007 17:51

Re: Flexible(re) Ladeprozedur
 
Gemeinsamheit zwischen den Version kannst du in der Basisklasse implementieren. Da liegt ja gerade der Vorteil von OOP.

Master-of-Magic 17. Mär 2007 17:57

Re: Flexible(re) Ladeprozedur
 
Die Gemeinsamkeiten kann ich aber nicht voraussehen. Mal ändert sich ein Variablentyp, dann wird hier mal ein Array erweitert oder dort ein ganz neues Array eingefügt ...

Im Prinzip sind Klassen eine gute Idee für die normale Verwendung in meinem Tool. Aber für die Ladeprozedur scheinen sie mir ungeeignet, da ich immer irgendwie eingeschränkt bin. :|


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:13 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz