Autor Beitrag
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8720
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mo 25.12.06 04:20 
Hi,

viele werden schon vor dem Problem gestanden haben, wie sie ihre Text-Daten aus einem Record oder Objekt auf Platte bekommen. Wenn man jedoch einfach versucht, auf gut Glück an der dünnsten Stelle zu bohren ("Wird schon klappen!"), wird man sich schnell mit einer der berühmten Access Violations, Buffer Overruns oder anderen unerwünschten Nebeneffekten ("Da kommt nur Müll an!") konfrontiert sehen ...

Was ich mit diesem kleinen Tutorial erklären möchte, ist der Hintergrund, warum diese Fehler auftreten und wie man sie vermeiden kann. Da sich dieses Tutorial speziell einem "Anfängerfehler" widmet, werd ich versuchen, ein paar Grundlagen mitzuliefern - ansonsten kann jederzeit ja gefragt werden - Naja ... der Umgang mit der F1-Taste bzw. FoSuFu sollte trotzdem gewohnt sein ;-)

Inhalt

  1. Der Blick hinter die Variablen
  2. So arbeitet Delphi damit
  3. So packt man sie in Dateien
  4. So packt man sie wieder aus
  5. Ein paar nützliche Quelltexte


Der Blick hinter die Variablen

Der Arbeitsspeicher eines Computers ist in viele einzelne Schubfächer aufgeteilt, in die man je nach Modell unterschiedlich viele Daten packen kann. Für einen Prozessor sind diese Daten aber immer nur Zahlen zwischen 0 und einer Grenze, die durch die Größe dieser Schubfächer festgelegt wird. Für einen - für die meisten gewohnten - Intel-Architektur-basierten Rechner ist diese Grenze auf Grund der 8 Bit (ein Byte) je Speicherzelle von 0 bis 255 gegeben. Möchte man nun in seinem Programm größere Zahlen oder gar Texte und Bilder darstellen, so muss man mehrere Speicherzellen zusammenfügen, um die gewünschte Information darzustellen. Demnach ist jegliche Information nur eine Frage der Interpretation.

Nehmen wir an, wir haben im Speicher folgendes Bild vorliegen:
ausblenden Quelltext
1:
05 00 00 00 42 65 6E 42 45 00 00 00					

So können wir dies als ein Array von Bytes (const Data: array[0..11of Byte = (5000661011106669000);), Array von Words (const Data: array[0..5of Word = (502592217006690);), als Serie von Integern (const Data: array[0..2of Integer = (5111453113869);), oder als Datenbereich eines AnsiStrings ('BenBE') aufgefasst werden. Jetzt werdet ihr euch sicherlich fragen, warum bei der letzten Interpretationsmöglichkeit eine andere Syntax verwendet wurde?. Der Grund dafür ist, dass Strings in Delphi kein wirklicher Grunddatentyp sind, sondern durch sehr viel Compiler Magic aufgebaut werden. Wenn also eine Variable vom Typ String (Speziell AnsiString, WideString) angelegt wird, so beinhaltet diese nicht die eigentliche Textinformation, sondern nur einen Zeiger auf das erste Zeichen.

Und wo bleiben die Variablen? Wir sind schon mitten drin! Variablen sind nun Namen für die Ablage solcher Informationen im Speicher. Variablen sind also nichts anderes als eine Benennung einer Speicherzelle, um diese einfacher wiederzufinden. Oder könnt ihr euch merken, ob ihr in die 3. oder 18. Speicherstelle die Antwort auf alle Fragen des Universums gespeichert habt? Also bietet Delphi die Möglichkeit, die Zuordnung der Speicherstellen für euch zu übernehmen - ihr müsst euch also nur noch um die richtigen Inhalte kümmern.

So arbeitet Delphi damit

Wenn also eine Variable bei Delphi deklariert wird, so reservert Delphi - abhängig davon, ob es die Größe der aufzunehmenden Informationen abschätzen kann oder nicht - entweder einen Speicherbereich, der die gewünschten Informationen direkt aufnimmt oder andernfalls den Platz für einen Zeiger (engl. Pointer), der auf die gespeicherten Informationen verweist, d.h. nur die Information enthält, wo etwas im Speicher liegt.

Hier mal ein kleiner Überblick:

Direkt gespeichert:

  • Alle Ordinal-Typen (Abhängig vom Wertebereich 1, 2, 4 oder 8 Byte)
  • Alle Gleitkomma-Typen (4, 8 oder 10 Byte)
  • Alle ShortStrings (1 Byte plus ein Byte je aufzunehmendem Zeichen)
  • Alle Record-Typen* (Summe der Größe aller Felder**)
  • Alle statischen Arrays (Anzahl Indizes * Größe des aufzunehmenden Datentyps)
  • Alle Pointer*** (4 Byte)


Über Pointer gespeichert:

  • Objekte, Klassen, Interfaces (Zeiger auf interne Verwaltungsstrukturen)
  • AnsiStrings, WideStrings (Zeiger auf erstes Zeichen)
  • dynamische Arrays (Zeiger auf erstes Element)


Und da ich schon Sterne verteilt habe, möchte ich diese auch kurz erklären, da sie zum Verständnis wichtig sind.
* Ein Record kann Felder von beliebigen Datentypen beinhalten. Soll hierbei nun ein Feld angelegt werden, was einen Datentyp besitzt, der nicht direkt gespeichert werden kann, so wird dies über einen Pointer realisiert, wie dies auch der Fall wäre, wenn eine normale Variable diesen Typ hätte.
** Wie unter * angedeutet zählt also nicht die Größe der tatsächlich referenzierten Daten, sondern nur die Größe, die für die Ablage der Variablen benötigt wird. Je nach verwendeter Feldausrichtung kann diese Größe oberhalb des eigentlichen Informationsgehaltes liegen. Dies gilt insbesondere, wenn der Record "variable" Strukturen enthält, da sich in diesem Fall die Größe des Records nach dem größten der variablen Abschnitte richtet.
*** Pointer nehmen unabhängig von den Informationen, auf die sie verweisen 4 Byte (unter 32-Bit-Systemen) in Anspruch, da sie lediglich einen Verweis darstellen und daher zur Laufzeit immer eine Konstante Größe - genug um jede mögliche Adresse des Adressraums von 4GB anzuzeigen - besitzen können, da sich die Größe des Adressraums nicht ändern kann.

Nehmen wir also ein Beispiel:

Gegeben sei also einmal folgende Deklaration eines Beispieltypen TExampleType
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Type
    TExampleType = array[Byte] of packed Record
        Field1: Integer;
        Field2: Byte;
        Field3: Extended;
        Field4: String[4];
    end;

Um nun den Speicherverbrauch dieses Typen festzustellen, gehen wir wie in der vorigen Liste aufgeführt die einzelnen Zeilen durch:

  1. Das Array umfasst wie im Index angegeben 256 Einträge eines Record-Typen, dessen Größe wir bisher noch nicht kennen.
  2. Der Record-Typ ist Packed (also hintereinander weg im Speicher abgelegt, um die Rechnung etwas zu vereinfachen und besteht aus vier Feldern.
  3. Field1 ist ein Integer und damit 4 Byte groß.
  4. Field2 ist ein Byte und damit 1 Byte groß.
  5. Field3 ist ein Extended und damit 10 Byte groß.
  6. Field4 ist ein ShortString, der maximal 4 Chars aufnehmen soll. Da zusätzlich ein Byte für die tatsächliche Längenangabe benötigt wird, benötigt dieses Feld 5 Byte im Speicher
  7. Die Gesamtgröße der 4 Record-Felder ist 4+1+10+5 = 20 Byte
  8. Das Array aus 256 solcher 20-Byte-Records benötigt also 5120 Byte (5KB).


Und nun noch mal eine kleine Darstellung, wie ein solcher Record-Eintrag aussehen könnte:
ausblenden Quelltext
1:
2:
3:
Offset:  | 00   01   02   03 | 04 | 05   06   07   08   09   0A   0B   0C   0D   0E | 0F   10   11   12   13 |
Feld #:  | F1   F1   F1   F1 | F2 | F3   F3   F3   F3   F3   F3   F3   F3   F3   F3 | F4   F4   F4   F4   F4 |
Daten:   | 39   05   00   00 | 2A | 00   00   00   00   00   00   00   80   FF   3F | 04   45   63   6B   65 |

Wobei diese Record folgende Informationen enthalten würde:
ausblenden Delphi-Quelltext
1:
2:
3:
const Data: TExampleType = (
    (Field1: 1337; Field2: 42; Field3: 1.0; Field4: 'Ecke'),
    {...}


Wenn man nun in seinem Delphi-Quelltext auf eines der Felder zugreift, so durchläuft Delphi dafür mehrere Schritte:

  1. Kenn ich die angeforderte Variable?
  2. Wo befindet sie sich im Speicher?
  3. Welchen Typ hat sie?
  4. Erfüllt der Datentyp die benötigten Voraussetzungen? (z.B. ist die Variable ein Record, wenn auf ein Feld zugegriffen wird?)
  5. Ist nur ein Teil der Variable gemeint? (z.B. Feld eines Records)
  6. Wo befindet sich die gewünschte Information endgültig? (nach Wiederholung der Schritte 2 bis 5)


Auch hierzu ein kurzes Beispiel: Welches Byte muss aus dem Speicher gelesen werden, wenn in Delphi der Ausdruck Data[5].Field4[4] auftaucht?
Nehmen wir an, die Konstante Data liegt im Speicher ab Offset $004012C0. So ergibt sich nach dem obigen Vorgehen also folgendes:

  1. (1) Ja, ich kenn sie ;-) - Nehmen wir mal an
  2. (2) Data liegt ab Offset $004012C0
  3. (3) Data ist ein statisches Array (mehr brauchen wir an dieser Stelle nicht zu wissen)
  4. (4) Ja, da als nächstes ein Index gegeben ist.
  5. (5) Wir brauchen nur einen Teil - nämlich Index 5
  6. (2) Da jeder Eintrag im Array 20 Byte umfasst und wir den Index 5 möchten, ergibt sich Offset := $004012C0 + 5 * 20 = $00401324;
  7. (3) Der Ausdruck Data[5] ist ein Record
  8. (4) Ja, da als nächstes auf ein Feld eines Records zugegriffen werden soll.
  9. (5) Wir brauchen nur ein Teilfeld - nämlich Field4
  10. (2) Da die Felder vor Field4 insgesamt 15 Bytes groß sind (4+1+10), ergibt sich Offset := $00401324 + 4+1+10 := $00401333;
  11. (3) Der Ausdruck Data[5].Field4 ist ein ShortString
  12. (4) Ja, da als nächstes auf ein Zeichen aus diesem String zugegriffen werden soll - nämlich das 4.
  13. (5) Wir brauchen nur einen Teil
  14. (2) Da ein ShortString ein Längenbyte und dahinter erst die Datenbytes umfasst, kann man den String-Index einfach addieren: Offset := $00401333 + 4 := $00401337;
  15. (3) Wir haben es mit einem Character (Char) zu tun.
  16. (4) Ja, Wir wollten das so ;-) - implizieren wir hier mal
  17. (5) Nein, da der Typ nicht weiter zerlegbar ist.
  18. (6) Die gewünschten Daten befinden sich an Offset $00401337 im Speicher.

Erstaunlich, was hinter solch einem winzigen Ausdruck an Arbeit für den Compiler steckt ;-)

So packt man sie in Dateien

Nun aber zum wichtigsten: Wie bekommt man die Daten nun aus dem RAM auf die Platte? Ganz einfach: Man sagt Delphi, die Adresse, ab der er aus dem Speicher kopieren soll. :mrgreen: Und das geht am besten mit Streams zu zeigen, da diese dieses Variablen-Prinzip am durchschaubarsten abbilden.

Als erstes müssen wir dazu aber einen solchen Dateistrom öffnen (ich nehm hier mal auf Grund des Ziels, die Daten auf Platte zu bekommen einen TFileStream - für andere Zielmedien geht das aber analog).

Also: Stream deklarieren:
ausblenden Delphi-Quelltext
1:
2:
var
    FS: TFileStream;

und initialisieren:
ausblenden Delphi-Quelltext
1:
FS := TFileStream.Create({Zieldateiname}{Zugriffsmethode});					


Nehmen wir nun an, dass wir den Integer und den Byte-Wert (also Field1 und Field2) aus unserem Data-Array (Sagen wir Index 16) speichern wollen, so geht das ganz einfach so hier
ausblenden Delphi-Quelltext
1:
2:
FS.WriteBuffer(Data[16].Field1, SizeOf(Data[16].Field1)); //Field1 speichern
FS.WriteBuffer(Data[16].Field2, SizeOf(Data[16].Field2)); //Field2 speichern

aber auch so hier:
ausblenden Delphi-Quelltext
1:
FS.WriteBuffer(Data[16].Field1, SizeOf(Data[16].Field1)+SizeOf(Data[16].Field2)); //Field1+Field2 speichern					

Glaubt ihr mir nicht? Dann probiert es mal aus, ODER schaut euch einmal an, was da im Speicher abläuft. Wie oben gezeigt liegen Field1 und Field2 IMMER hintereinander im Speicher, d.h. man kann beide Datenfelder "zu einem zusammenfassen", indem man die Größer der beiden addiert. Das funktioniert aber nur, wenn die zu speichernden Felder direkt hintereinander liegen - also VORSICHT!!!

Auch bei dem ShortString in Field4 ist die gezeigte Methode, einfach auf das Feld zuzugreifen, korrekt, da es sich um einen "inline" gespeicherten Datentyp handelt (vgl. die Auflistung). Eine Zeile wie FS.WriteBuffer(Data[16].Field4, SizeOf(Data[16].Field4)); //Field4 speichern ist also völlig legitim.

Anders sieht die Sache aber aus, wenn Ihr den Inhalt eines z.B. Edit-Feldes speichern wollt und dieser in Form eines Strings vorliegt:
ausblenden Delphi-Quelltext
1:
2:
3:
var
    StrDataLen: Integer;
    StrData: String;

Wie oben aus der Liste zu entnehmen ist, werden Strings nicht "inline" gespeichert, sondern enthalten nur eine Referenz auf die eigentlichen Daten. Daher führt eine Zeile wie
ausblenden Delphi-Quelltext
1:
FS.WriteBuffer(StrData, SizeOf(StrData)); //StrData speichern					

garantiert nicht zum Erfolg, wenn es euch auf den Inhalt des Strings ankommt. Was Delphi an dieser Stelle speichert ist nicht der String, sondern die ersten 4 Byte (SizeOf(String) = 4) des Speicherplatzes, an dem die Variable StrData steht, mit anderen Worten: Delphi speichert euch den Zeiger auf die Stringdaten, der sich jederzeit ändern kann.
Was man also hier tun muss, ist Delphi anzuweisen, nicht den Pointer auf die Stringdaten aufzusuchen, sondern das erste Zeichen des Strings.
ausblenden Delphi-Quelltext
1:
FS.WriteBuffer(StrData[1], Length(StrData)); //StrData speichern					

ist also schon fast unser Ziel ... wie gesagt: Fast. Denn jemand der diese Datei lesen möchte, wüsste nun noch nicht, wie lang der geschriebene String ist. Das geht aber ganz einfach: Man speichert vor dem eigentlichen String dessen Länge:
ausblenden Delphi-Quelltext
1:
2:
3:
StrDataLen := Length(StrData);
FS.WriteBuffer(StrDataLen, SizeOf(StrDataLen)); //StrData (Länge) speichern
FS.WriteBuffer(StrData[1], StrDataLen); //StrData (Daten) speichern

That's it!

So packt man sie wieder aus
Jetzt nur noch wieder rankommen ... Das ist aber nach Abschluss des vorigen Kapitels gar nicht mehr so schwer!
Für Grunddatentypen (also alles, waseinzig aus "Inline"-Datentypen besteht, brauch man einfach nur Write durch Read beim Zugriff ersetzen - schon ist man fertig.


Für komplexere Datentypen muss man wieder etwas mehr Mühen aufwenden; das ist aber auch nicht unschaffbar. Was man brauch ist doch eigentlich nur die Sache umkehren: Also Speicher bereitstellen für die Variable und danach die Daten reinschreiben:

ausblenden Delphi-Quelltext
1:
2:
3:
FS.ReadBuffer(StrDataLen, SizeOf(StrDataLen)); //StrData (Länge) lesen
SetLength(StrData, StrDataLen) //Speicher für StrData reservieren
FS.WriteBuffer(StrData[1], StrDataLen); //StrData (Daten) lesen


Auch hier wären wir also fertig. Einfach, nicht?

Nicht ganz - Nach getaner Arbeit - und das gilt nicht nur für's Lesen - sollten wir noch unsere verwendeten Ressourcen Freigeben!

Also nicht vergessen FreeAndNil(FS); oder auch alternativ FS.Free; aufzurufen.

Ein paar nützliche Quelltexte

Wem das aber alles zu kompliziert erscheint, für den gibt's hier noch eine kleine Zusammenfassung und anschließend eine Hilfestellung:


  • Beim Lesen und Schreiben sollte man darauf achten, wie groß seine Daten sind UND wo sie liegen (inline oder referenziert?)
  • Das Schreiben eines Integers (oder anderen Inline-Datentyps ohne Referenzen) sieht so aus:
    ausblenden Delphi-Quelltext
    1:
    2:
    3:
    4:
    5:
    6:
    7:
    8:
    9:
    10:
    11:
    Procedure SaveInt(I: Integer);
    var
        FS: TFileStream;
    begin
        FS := TFileStream.Create({Zieldateiname}{Zugriffsmethode});
        try
            FS.WriteBuffer(I, SizeOf(I));
        finally
            FreeAndNil(FS);
        end;
    end;

  • Das Lesen eines Integers (oder anderen Inline-Datentyps ohne Referenzen) sieht so aus:
    ausblenden Delphi-Quelltext
    1:
    2:
    3:
    4:
    5:
    6:
    7:
    8:
    9:
    10:
    11:
    Procedure LoadInt: Integer);
    var
        FS: TFileStream;
    begin
        FS := TFileStream.Create({Quelldateiname}{Zugriffsmethode});
        try
            FS.ReadBuffer(Result, SizeOf(Result));
        finally
            FreeAndNil(FS);
        end;
    end;


  • Das Schreiben eines Strings sieht so aus:
    ausblenden Delphi-Quelltext
    1:
    2:
    3:
    4:
    5:
    6:
    7:
    8:
    9:
    10:
    11:
    12:
    13:
    14:
    15:
    Procedure SaveStr(S: String);
    var
        FS: TFileStream;
        I: Integer;
    begin
        FS := TFileStream.Create({Zieldateiname}{Zugriffsmethode});
        try
            I := Length(S);
            FS.WriteBuffer(I, SizeOf(I));
            If I <> 0 Then
                FS.WriteBuffer(S[1], I);
        finally
            FreeAndNil(FS);
        end;
    end;

  • Das Lesen eines Strings sieht so aus:
    ausblenden Delphi-Quelltext
    1:
    2:
    3:
    4:
    5:
    6:
    7:
    8:
    9:
    10:
    11:
    12:
    13:
    14:
    15:
    Procedure LoadStr: String;
    var
        FS: TFileStream;
        I: Integer;
    begin
        FS := TFileStream.Create({Quelldateiname}{Zugriffsmethode});
        try
            FS.ReadBuffer(I, SizeOf(I));
            SetLength(Result, I);
            If I <> 0 Then
                FS.ReadBuffer(S[1], I);
        finally
            FreeAndNil(FS);
        end;
    end;

  • Speichern von dynamischen Arrays:
    Genauso wie Strings auch, jedoch die Größe je Array-Element beachten
  • Laden von dynamischen Arrays:
    Genauso wie Strings auch, jedoch die Größe je Array-Element beachten
  • Speichern und Lesen von Objekten
    Frag das Objekt, dass es die Operation für dich ausführt ;-)


Und zu guter Letzt noch eine kleiner Tipp für ganz Faule: Schaut euch doch einmal die Klasse TOVFSStream in der Unit OVFSManager.pas an. Sie beinhaltet für die wesentlichen Grundtypen eine direkte Methode zum Lesen und Schreiben, muss aber als Kapselungsstream verwendet werden, was sicherlich nicht jedermanns Sache ist.

So viel erst einmal von meiner Seite. Ich wünsche euch noch viel Erfolg bei euren Projekten und hoffe mit diesem kleinen Tutorial ein wenig Licht in das Dunkel so mancher Erscheinung bei der Daten-Ein-\Ausgabe gebraucht zu haben.

MfG,
BenBE.


Moderiert von user profile iconjasocul: Topic aus Neue Einträge / Hinweise / etc. verschoben am Di 26.12.2006 um 12:00
Moderiert von user profile iconBenBE: Kleineren Datenfehler in einem Memdump korrigiert am So 28.01.2007 um 17:43

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.


Zuletzt bearbeitet von BenBE am So 28.01.07 18:45, insgesamt 2-mal bearbeitet
Barzi
Ehemaliges Mitglied
Erhaltene Danke: 2



BeitragVerfasst: Mo 25.12.06 17:26 
Wahnsinn! Hattest du nichst zu tun oder hast du grad ein 10-Finger-Tippkurs gemacht :D ....
Nein, also dein Toturial ist echt genial...

Großes THX dafür!
-Pl-
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 22

Win XP, Win Vista
Delphi 7
BeitragVerfasst: So 28.01.07 18:37 
user profile iconBenBE hat folgendes geschrieben:

Und nun noch mal eine kleine Darstellung, wie ein solcher Record-Eintrag aussehen könnte:
ausblenden Quelltext
1:
2:
3:
Offset:  | 00   01   02   03 | 04 | 05   06   07   08   09   0A   0B   0C   0D   0E | 0F   10   11   12   13 |
Feld #:  | F1   F1   F1   F1 | F2 | F3   F3   F3   F3   F3   F3   F3   F3   F3   F3 | F4   F4   F4   F4   F4 |
Daten:   | 39   05   00   00 | 2D | 00   00   00   00   00   00   00   80   FF   3F | 04   45   63   6B   65 |

Wobei diese Record folgende Informationen enthalten würde:
ausblenden Delphi-Quelltext
1:
2:
3:
const Data: TExampleType = (
    (Field1: 1337; Field2: 42; Field3: 1.0; Field4: 'Ecke'),
    {...}



Nur mal ganz doof gefragt und eigentlich ist es auch absolut nicht wichtig, aber müsste 2D (Feld 2) nicht 45 sein ?

Ansonsten super Tut, hab das meiste sogar verstanden. ;-)

Vielen Dank
BenBE Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8720
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: So 28.01.07 18:42 
user profile icon-Pl- hat folgendes geschrieben:
user profile iconBenBE hat folgendes geschrieben:

Und nun noch mal eine kleine Darstellung, wie ein solcher Record-Eintrag aussehen könnte:
ausblenden Quelltext
1:
2:
3:
Offset:  | 00   01   02   03 | 04 | 05   06   07   08   09   0A   0B   0C   0D   0E | 0F   10   11   12   13 |
Feld #:  | F1   F1   F1   F1 | F2 | F3   F3   F3   F3   F3   F3   F3   F3   F3   F3 | F4   F4   F4   F4   F4 |
Daten:   | 39   05   00   00 | 2D | 00   00   00   00   00   00   00   80   FF   3F | 04   45   63   6B   65 |

Wobei diese Record folgende Informationen enthalten würde:
ausblenden Delphi-Quelltext
1:
2:
3:
const Data: TExampleType = (
    (Field1: 1337; Field2: 42; Field3: 1.0; Field4: 'Ecke'),
    {...}



Nur mal ganz doof gefragt und eigentlich ist es auch absolut nicht wichtig, aber müsste 2D (Feld 2) nicht 45 sein ?

Jup. Korrekt ... Ich glaube, dieser Fehler ist mir aber angesichts der Uhrzeit dieses Tuts zu verzeihen ... Bin mit Narses eh gerade dabei, das Tut Schritt für Schritt noch etwas zu überarbeiten.

user profile icon-Pl- hat folgendes geschrieben:
Ansonsten super Tut, hab das meiste sogar verstanden. ;-)

Vielen Dank

Das klingt doch gut ;-) Die Stellen, wo Du noch hängst wären da glaube am Interessantesten, damit man weiß, wo man noch pfeilen sollte ^^

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
-Pl-
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 22

Win XP, Win Vista
Delphi 7
BeitragVerfasst: Di 30.01.07 14:24 
user profile iconBenBE hat folgendes geschrieben:

Jup. Korrekt ... Ich glaube, dieser Fehler ist mir aber angesichts der Uhrzeit dieses Tuts zu verzeihen ...

Nein auf keinen Fall :D

user profile iconBenBE hat folgendes geschrieben:

Das klingt doch gut ;-) Die Stellen, wo Du noch hängst wären da glaube am Interessantesten, damit man weiß, wo man noch pfeilen sollte ^^


Stimmt wohl, aber so wichtig ist der Spaß zur Zeit nicht für mich, deshalb werde ich das vernachlässigen bis ich es wirklich mal brauche. ;)