Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Pointer und Strings


knittel - Mo 15.07.13 01:59
Titel: Pointer und Strings
Hallo allerseits,

Ich schreibe mir eine Wrapper-Klasse um TFileStream (habe den topic trotzdem hier gepostet, weil das Problem etwas anderes betrifft), habe aber damit ein Problem. Ich habe mir eine Funktion zum Speichern geschrieben und eine zum Laden und die sieht wie folgt aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
function TMeFile.LoadNextElement(p: pointer): Byte;
var i, n: integer; str: string;
begin
FStream.Read(result, 1); // Rückgabewert ist das Identifikationsbyte
case result of
  0begin // 0 = Unbekanntes Format
    FStream.Read(n, 4);
    FStream.Read(p^, n);
    end;
  1begin // 1 = String laden
    FStream.Read(n, 4); // er lädt die Länge des Strings
    SetLength(str, n); // Länge setzen.
    if (n > 0then FStream.Read(pointer(str)^, n); // wenn die Länge größer 0 ist, soll er den String laden.
    // und hier beginnt das Problem, wie übergebe ich den String nun über den Pointer zurück??? Das hier unten ist mein verrückter Versuch.
    SetLength(string(p^), n); 
    Move(str[1], p^, n);
    end;
  2: FStream.Read(p^, 4);
  3: FStream.Read(p^, 4);
  end;
end;


Ich möchte halt, dass ich einfach nur schön MyFile.LoadNextElement(@MyInteger), MyFile.LoadNextElement(@MyString) benutzen kann. Mit meinem Code funktioniert das leider nicht so ganz. Ich hab zwar vom Umgang mit Pointern ein bisschen Ahnung, aber nur solange es sich um Listen/etc. handelt, leider nicht bei Strings. Könnt ihr mir irgendwie helfen und schauen, was ich an meinem Code verändern muss?

Danke schonmal! :)


jaenicke - Mo 15.07.13 08:43

Das Problem ist die Referenzzählung. Ein String, den du innerhalb der Methode erstellst, wird an deren Ende freigegeben. Wenn du einen Pointer darauf zurücklieferst, ist der daher nach dem Ende der Methode ungültig. (Davon abgesehen gehst du davon aus, dass ein Zeichen immer ein Byte hat, das würde mit neueren Delphiversionen nicht mehr funktionieren.)

In Zeile 8 schreibst du einfach in den Pointer, obwohl du gar nicht weißt, ob dort genug Platz ist. Keine gute Idee...

Grundsätzlich sollte immer die aufrufende Methode den Speicher reservieren und deine Methode diesen nur füllen. In neueren Delphiversionen würden sich dafür Generics oder anonyme Methoden anbieten, aber beides hatte Delphi 7 (wie in deinem Profil) damals nicht.

Was du machen kannst: Genauso wie die ReadBuffer und WriteBuffer Methoden nicht nur den Pointer angeben, sondern auch wie viel Platz darin ist. Ist nicht genug Platz, setzt du die Streamposition auf den Wert vor dem Leseversuch zurück und lieferst die benötigte Puffergröße zurück. So funktioniert es auch in der Windows API. Sprich:
Anfrage mit nil und Puffergröße 0, Rückgabe von 10 als Größe in Byte, SetLength auf den String, erneuter Aufruf mit dem Pointer auf den String und Puffergröße 10.

Wirklich elegant ist eine solche Kapselung allerdings erst mit Generics...


IhopeonlyReader - Mo 15.07.13 15:31

Ich würde sagen, der Fehler beginnt schon beim Aufbau der Procedure, du rufst die procedure auf ohne zu wissen was du empfängst musst aber schon etwas übergeben (z.B. Integer-Pointer)
Deshalb würde ich dir empfehlen den Identifikationsbyte VOR aufruf der procedure auszulesen und danach entscheiden welche Variable übergeben werden muss, sonst wird's mit dem weiterarbeiten schwer...
die Case-Abfrage kannst du also eigentlich aus der Procedure rausziehen..
Dann z.B. bei einem unbekannten Format, das ganze "ignorieren", da du damit eh nicht weiterarbeiten wirst, da du ja nicht weißt was es ist..
bei einem String ruft du dann eine extra dafür geschriebene Procedure auf und übergibst als Parameter schon ein String
Ist es eine Integer-Variable rufst du eine LoadIntegerFromStream-Procedure auf (selbergeschriebene) und übergibst als Parameter eine Integer Variable..

Eine Umsetzung mit Pointerübergabe halte ich allgemein für ungut.. Tut mir leid, aber ich sehe da keinen Sinn drin, etwas in eine Gruppe einzuordnen, wenn man den Typ eh wieder "verschinden lässt" über den Pointer...
Da könntest du auch

Delphi-Quelltext
1:
2:
3:
FStream.Read(n, 4);
if n<=SizeOf(p^) then
 FStream.Read(p^, n);

wäre so wie ich das sehe das gleiche, da du den Typ außerhalb deine Procedure scheinbar nicht beachtest


knittel - Mo 15.07.13 16:44

Danke! Ich hab das jetzt genausogemacht wie du vorgeschlagen hattest und für jeden Typ eine eigene Load Prozedur geschrieben. Habs jetzt ausprobiert und funktioniert prima! Danke nochmal.