Autor Beitrag
Stefan S.
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 184


D5
BeitragVerfasst: Mo 19.01.09 17:08 
Hallo allerseits.

Ich arbeite an einer Speichern-/Laden-Funktion für Benutzereinstellungen, für die ich FileSteams verwende. Für Dateitypen wie Integer, Floats (Extended) oder Booleans habe ich das ganze auch bereits hinbekommen. Als es dann ans Lesen und Schreiben von Strings ging, musste ich die Forensuche bemühen und bin auf folgende Funktion/Prozedur gestoßen, die ein User gepostet hat:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
function ReadStringFromStream(F: TFileStream): String;
var
   aStrLen : Integer;
begin
  F.ReadBuffer(aStrLen,SizeOf(Integer));
  SetLength(Result,aStrLen);
  if aStrLen > 0 then
    F.ReadBuffer(Pointer(Result)^,aStrLen);
end;

procedure WriteStringToStream(F: TFileStream; aStr: String);
var
   aStrLen : Integer;
begin
  aStrLen:=Length(aStr);
  F.WriteBuffer(aStrLen,SizeOf(Integer));
  if aStrLen > 0 then
    F.WriteBuffer(Pointer(aStr)^,aStrLen);
end;


Die habe ich dann verwendet. Nachdem ich jetzt aber auch Strings in die Datei schreibe, funktioniert gar nichts mehr, auch andere Dateitypen, die im Code vor den Strings geschrieben werden, können nicht mehr korrekt ausgelesen werden. Wahrscheinlich weil die erstellten Dateien aus irgendeinem Grund totales Chaos sind.

Hier meine Speichern-Prozedur:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
procedure Speichern(Dateiname: String);
var Datei: TFileStream;
    i: Byte;
begin
  //Anlegen des FileStreams
  if FileExists(Dateiname) then
    Datei := TFileStream.Create(Dateiname,fmOpenWrite)
  else
    Datei := TFileStream.Create(Dateiname,fmCreate);
  Datei.Position := 0;
  //Daten schreiben
  Datei.WriteBuffer(UserVar_Anzahl,sizeof(UserVar_Anzahl));
  if UserVar_Anzahl > 0 then
   begin
    for i := 1 to UserVar_Anzahl do
      with User_Variablen[i] do
       begin
        Datei.WriteBuffer(Buchstabe,sizeof(Buchstabe));
        Datei.WriteBuffer(Durchlauf,sizeof(Durchlauf));
        Datei.WriteBuffer(Wert,sizeof(Wert));
        Datei.WriteBuffer(Start,sizeof(Start));
        Datei.WriteBuffer(Ende,sizeof(Ende));
        Datei.WriteBuffer(Schritt,sizeof(Schritt));
       end;
   end;
  Datei.WriteBuffer(ObjektNr,sizeof(ObjektNr));
  Datei.WriteBuffer(ModTrans_Anzahl,sizeof(ModTrans_Anzahl));
  Datei.WriteBuffer(ViewTrans_Anzahl,sizeof(ViewTrans_Anzahl));
  if ModTrans_Anzahl > 0 then
   begin
    for i := 1 to ModTrans_Anzahl do
      with ModTrans[i] do
       begin
        Datei.WriteBuffer(aktiv,sizeof(aktiv));
        Datei.WriteBuffer(Modus,sizeof(Modus));

        Datei.WriteBuffer(x.Vars,sizeof(x.Vars));
        Datei.WriteBuffer(x.Wert,sizeof(x.Wert));
        WriteStringToStream(Datei,x.Eingabe);

        Datei.WriteBuffer(y.Vars,sizeof(y.Vars));
        Datei.WriteBuffer(y.Wert,sizeof(y.Wert));
        WriteStringToStream(Datei,y.Eingabe);

        Datei.WriteBuffer(z.Vars,sizeof(z.Vars));
        Datei.WriteBuffer(z.Wert,sizeof(z.Wert));
        WriteStringToStream(Datei,z.Eingabe);

        Datei.WriteBuffer(Winkel.Vars,sizeof(Winkel.Vars));
        Datei.WriteBuffer(Winkel.Wert,sizeof(Winkel.Wert));
        WriteStringToStream(Datei,Winkel.Eingabe);
       end;
   end;
  //Freigeben des FileStreams
  FreeAndNil(Datei);
end;


Die einzelnen Dateitypen in den Records, die ich verwende, dürften dabei nicht relevant sein, das X.Eingabe ist vom Typ ShortString.

Die Lesen-Prozedur ist im Prinzip das selbe mit ReadBuffer statt Write Buffer und der Anzahl Bytes des jeweiligen Datentyps als Parameter. ^^ Der Fehler muss irgendwie mit den Strings zu tun haben, weil es ja wie gesagt vorher funktioniert hat. Aber an der Prozedur und der Funktion aus dem Forum finde ich persönlich nichts... Kann gut sein dass es irgendein Anfängerfehler meinerseits ist, da ich in Sachen FileStreams noch nicht so bewandert bin. ^^


Moderiert von user profile iconNarses: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Mo 19.01.2009 um 18:16
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10185
Erhaltene Danke: 1261

W11x64
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Mo 19.01.09 18:16 
Moin!

user profile iconStefan S. hat folgendes geschrieben Zum zitierten Posting springen:
Die einzelnen Dateitypen in den Records, die ich verwende, dürften dabei nicht relevant sein, das X.Eingabe ist vom Typ ShortString.
Die Möglichkeit besteht, allerdings werden wir es erst dann sicher wissen, wenn du mal die Deklaration der Variablen zeigst, die du da in den Stream schreibst. ;)

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 19.01.09 20:00 
user profile iconStefan S. hat folgendes geschrieben Zum zitierten Posting springen:
das X.Eingabe ist vom Typ ShortString.
Das ist bei den angegebenen Methoden zum Lesen und Schreiben des Strings ja gar nicht nötig, es können auch normale Strings beliebiger Länge (naja übertreiben muss man es nicht :D) sein.

Im Hinblick auf Delphi 2009 würde ich das aber eher so machen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
function ReadStringFromStream(F: TFileStream): String;
var
   aStrLen: Integer;
begin
  F.ReadBuffer(aStrLen, SizeOf(Integer));
  SetLength(Result, aStrLen);
  if aStrLen > 0 then
    F.ReadBuffer(Pointer(Result)^, aStrLen * SizeOf(Char));
end;

procedure WriteStringToStream(F: TFileStream; aStr: String);
var
   aStrLen: Integer;
begin
  aStrLen := Length(aStr);
  F.WriteBuffer(aStrLen, SizeOf(Integer));
  if aStrLen > 0 then
    F.WriteBuffer(Pointer(aStr)^, aStrLen * SizeOf(Char));
end;
Grund: Ab Delphi 2009 ist ein Buchstabe nicht mehr unbedingt ein Byte groß. Deshalb sollte man bei der Anzahl der Bytes zum Schreiben und Lesen nicht davon ausgehen, dass jeder Buchstabe 1 Byte groß ist, sondern sollte die Größe in Bytes lieber berechnen.

Auf diese Weise funktioniert es in älteren Delphiversionen genauso wie in Delphi 2009. ;-)

Ansonsten schließe ich mich user profile iconNarses an was das Problem betrifft.
Luckie
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Di 20.01.09 09:59 
user profile iconStefan S. hat folgendes geschrieben Zum zitierten Posting springen:
Ich arbeite an einer Speichern-/Laden-Funktion für Benutzereinstellungen, für die ich FileSteams verwende.

Warum machst du dir das Leben unnötig schwer? Benutz doch Ini-Dateien oder wenn es unbedingt sein muss XML-Dateien. Zur Not würde es auch eine Value-StringList tun. Mit Ini-Dateien wärst du jetzt wahrscheinlich schon fertig und dein Chef glücklich. ;)
Stefan S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 184


D5
BeitragVerfasst: Di 20.01.09 14:39 
Manchmal kann es schon helfen, dass man versucht, anderen das eigene Problem zu erklären. ^^ Während ich gerade die Deklaration meiner Variablen posten wollte, bin ich selber drauf gekommen: Neben einem kleinen (aber entscheidenden) Fehler bei der Angabe der Byte-Zahl war etwas schuld, was mit den FileStreams selber wenig zu tun hatte. "ModTrans" ist ein dynamisches Array das von 0 bis X geht, die Variable "ModTrans_Anzahl" gibt dagegen die Anzahl der Elemente von 1 bis X+1 an. Da meine Schleife aber die "ModTrans"-Elemente von 1 bis "ModTrans_Anzahl" (statt von 0 bis "ModTrans_Anzahl"-1) in die Datei geschrieben hat, stand in der entsprechender Nonsens.

Wie es immer wieder diese Leichtsinnsfehler sind, die einen so lange beschäftigen... Trotzdem danke an alle für die Hilfe. :)

@jaenicke: OK, danke, Ratschlag berücksichtigt.

@Luckie: Hab ich auch schon überlegt. Es sind aber nicht Benutzereinstellungen, die beim Programmstart automatisch geladen werden, sie entsprechen eher einem "Projekt", das heißt der Benutzer kann beliebig viele dieser Settings anlegen. Dafür denke ich mal sind Ini-Dateien nicht gedacht.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 20.01.09 15:09 
user profile iconStefan S. hat folgendes geschrieben Zum zitierten Posting springen:
@Luckie: Hab ich auch schon überlegt. Es sind aber nicht Benutzereinstellungen, die beim Programmstart automatisch geladen werden, sie entsprechen eher einem "Projekt", das heißt der Benutzer kann beliebig viele dieser Settings anlegen. Dafür denke ich mal sind Ini-Dateien nicht gedacht.
Man kann auch das mit INIs lösen und mit XML-Dateien sogar sehr gut.

Bei einer guten Implementierung ist aber eine eigene Umsetzung schneller, weil man die Datei nicht parsen muss sondern gezielt bestimmte Stellen auslesen kann. Das habe ich bei einem Bibliotheksprogramm gemerkt, bei dem ich sehr viele Datensätze beim Start auslesen musste.
(Letztlich habe ich es so geschafft das Auslesen so stark zu beschleunigen, dass ich die Fortschrittsanzeige weglassen konnte, weil man die eh kaum noch sah.)