Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Komisches Verhalten mit dyn. Array of TVarRec


Knulli - Mi 20.03.19 18:30
Titel: Komisches Verhalten mit dyn. Array of TVarRec
Hi Leute,

ich möchte gerne die Format-Funktion mit einem dynamischen Array als Argumentenliste aufrufen.
--> Format(stFmt, Args)
Den FormatString und das Array für die Argumente möchte ich mir in Abhängigkeit diverser Bedingungen selber zusammenbasteln.

Leider zeschießt sich das Array beim dynamischen längermachen von selbst.
Ab dem vierten Eintrag wird der LastIDX-2 mit überschrieben.

Was mache ich falsch?


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:
{**************************************************************************************************}
procedure TForm1.Button1Click(Sender: TObject);
type
  TMyVarRecArray = Array of TVarRec;
var
  stFMT: String;
  stResult: String;
  Args: TMyVarRecArray;
  Loop: Integer;
  //////////////////////////////////////////////////////////////////////////////////////////////////
  procedure AddToVarRecArray(var aVarRecArray: TMyVarRecArray; aValues: Array of const);
  var
    VarRec: TVarRec;
  begin
    for VarRec in aValues do
    begin
      SetLength(aVarRecArray, Length(aVarRecArray) + 1);
      Args[High(aVarRecArray)] := VarRec;
    end;
  end;
  //////////////////////////////////////////////////////////////////////////////////////////////////
begin

  stFmt := 'Das wird eine Zeile: '; SetLength(Args, 0);
  for Loop := 1 to 5 do
  begin
    stFmt := stFmt + ' %s ';
    AddToVarRecArray(Args, ['Wert ' + IntToStr(Loop)]);
  end;
  stResult := Format(stFmt, Args);
  MessageBox(0, PChar(stResult), 'NaNu', MB_OK);
end;
{**************************************************************************************************}


Knulli


GuaAck - Mi 20.03.19 19:18

Hallo,

diese Zeile verstehe ich nicht und mein Compiler will si auch nicht übersetzen:


Delphi-Quelltext
1:
   for VarRec in aValues do                    


Gruß
GuaAck


jasocul - Do 21.03.19 08:25

In Delphi 7 gibt es diese Möglichkeit noch nicht.
Daher meckert dein Compiler

Zum eigentlichen Problem:
VarRec ist ein Zeiger auf einen temporären Speicher, der in der Schleife immer wieder genutzt wird.
Durch die Vergrößerung deines Records reservierst du zwar neuen Speicher, aber weist dann doch einen anderen (den oben genannten temporären) Speicherbereich zu.
Dadurch, dass dieser Speicher immer wieder benutzt wird, bekommst du deine falschen Werte.

Das kann soweit führen, dass du im späteren Programmverlauf immer wieder andere Informationen in deinem Array stehen hast, weil der Speicher durch andere Daten überschrieben werden kann. Abgesehen davon dürftest du dadurch auch ein Memoryleak bekommen.

Du musst also bei den Zuweisung in dein Array die Inhalte und nicht die Zeiger zuweisen.
In TVarRec stehen unter VType auch Typ-Informationen. Bei AnsiStrings (und nicht nur da) gibt es noch das zusätzliche Problem, dass die auch nur als Zeiger im Record stehen. Auch dieser Speicherbereich ist nur temporär und wird innerhalb von Delphi sicher wieder mit anderen Informationen während des Programm gefüllt werden können.


Knulli - Do 21.03.19 15:12

OK, habs (hoffentlich) begiffen:
In TVarRec werden nur Referenzen gespeichert. Und weil bei meinem Aufruf von AddToVarRecArray das Argument nur auf dem Stack existiert (also innerhalb von AddToVarRecArray), gabs Datensalat bei den nachfolgenden Aufrufen.
Ich muss also bis zum Ende der "Nutzzeit" von Args ( also bis nach nach Format(...) ) den Speicherplatz für die Argumente vorhalten. Richtig verstanden?


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:
{**************************************************************************************************}
procedure TForm1.Button1Click(Sender: TObject);
type
  TMyVarRecArray = Array of TVarRec;
var
  stFMT: String;
  stResult: String;
  Args: TMyVarRecArray;
  Strings: Array of String;                             // Hier stehen die Strings WIRKLICH
  Loop: Integer;
  //////////////////////////////////////////////////////////////////////////////////////////////////
  procedure AddToVarRecArray(var aVarRecArray: TMyVarRecArray; aValues: Array of const);
  var
    VarRec: TVarRec;
  begin
    for VarRec in aValues do
    begin
      SetLength(aVarRecArray, Length(aVarRecArray) + 1);
      Args[High(aVarRecArray)] := VarRec;
    end;
  end;
  //////////////////////////////////////////////////////////////////////////////////////////////////
begin

  stFmt := 'Das wird eine Zeile: '; SetLength(Args, 0);
  for Loop := 1 to 5 do
  begin
    stFmt := stFmt + ' %s ';
    SetLength(Strings, Length(Strings) + 1);            // Platz schaffan
    Strings[High(Strings)] := 'Wert ' + IntToStr(Loop); // Argument zuweisen
    AddToVarRecArray(Args, [Strings[High(Strings)]]);   // Argument(e) als Referenz(en) übergeben
  end;
  stResult := Format(stFmt, Args);
  SetLength(Strings, 0);                                // erst jetzt darf der Speicher wieder freigegeben werden
  MessageBox(0, PChar(stResult), 'Hubba Hubba!!', MB_OK);
end;
{**************************************************************************************************}


Also kann ich das TVarRecArray nicht WIRKLICH effektiv nutzen. :-(

Frisst Format noch andere Arrays / Listen, die sich auch die WERTE statt nur Referenzen merken können?

Knulli


jasocul - Fr 22.03.19 08:57

user profile iconKnulli hat folgendes geschrieben Zum zitierten Posting springen:

Also kann ich das TVarRecArray nicht WIRKLICH effektiv nutzen. :-(

Es ist zumindest nicht trivial.
Ich habe nochmal etwas recherchiert, weil ich zu wenig Zeit habe, eine Lösung für dich zu erarbeiten, die brauchbar ist. Dabei habe ich folgendes gefunden:
https://stackoverflow.com/questions/6058697/how-to-set-string-or-ansistring-constant-in-the-tvarrec [https://stackoverflow.com/questions/6058697/how-to-set-string-or-ansistring-constant-in-the-tvarrec]
Insbesondere die letzte Antwort beschreibt einen Lösungsansatz, der dein Problem lösen kann. Ist aber dennoch etwas Aufwand.
user profile iconKnulli hat folgendes geschrieben Zum zitierten Posting springen:

Frisst Format noch andere Arrays / Listen, die sich auch die WERTE statt nur Referenzen merken können?

Format habe ich bisher noch nie genutzt. Da muss vielleicht mal ein anderer User was zu sagen.


jaenicke - Fr 22.03.19 10:04

In dem vorliegenden Fall ist es relativ simpel, da du ja das Array nur innerhalb deiner Methode benötigst. Es genügt daher, wenn du deine Strings in einer lokalen Variable hast (was ja schon der Fall ist) und du nicht versuchst den String in den Record zu bekommen, sondern (wie in den Links gezeigt) nur die Pointer auf diese vorhandenen Strings.

So habe ich das auch schon für die Verwendung mit Format gemacht.

user profile iconjasocul hat folgendes geschrieben Zum zitierten Posting springen:
Format habe ich bisher noch nie genutzt.
Wir benutzen das um Ausgabestrings nicht per Hand zusammenzuklöppeln, was einfach die Übersichtlichkeit verringern würde und die Übersetzung in andere Sprachen deutlich erschweren oder unmöglich machen würde.


jasocul - Fr 22.03.19 11:03

Off-Topic:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjasocul hat folgendes geschrieben Zum zitierten Posting springen:
Format habe ich bisher noch nie genutzt.
Wir benutzen das um Ausgabestrings nicht per Hand zusammenzuklöppeln, was einfach die Übersichtlichkeit verringern würde und die Übersetzung in andere Sprachen deutlich erschweren oder unmöglich machen würde.

Genau dieser Bedarf existiert bei uns nicht/kaum, da wir nur Inhouse-Entwicklung machen. Es gibt zwar Anwendungen, die Protokolle erzeugen, aber die werden nur von der IT genutzt. Großartige Formatierungen sind daher nicht erforderlich.

On-Topic:
Die Strings sind eben nicht in einer lokalen Variablen. Es wird als Konstante (in einem Array of const) an die Verarbeitungsroutine übergeben. Somit wird der reservierte Speicher wieder frei gegeben und im späteren Schleifendurchlauf wieder genutzt. Das führt ja genau zu dem beschriebenen Problem.


jaenicke - Fr 22.03.19 11:42

user profile iconjasocul hat folgendes geschrieben Zum zitierten Posting springen:
Die Strings sind eben nicht in einer lokalen Variablen. Es wird als Konstante (in einem Array of const) an die Verarbeitungsroutine übergeben.
Doch, sind sie, in einer der Versionen:
Zitat:

Delphi-Quelltext
1:
2:
    Strings[High(Strings)] := 'Wert ' + IntToStr(Loop); // Argument zuweisen
    AddToVarRecArray(Args, [Strings[High(Strings)]]);   // Argument(e) als Referenz(en) übergeben
An der Stelle muss jeder String wie in dem Link beschrieben als Pointer in das Argument geschrieben werden statt wie aktuell wiederum ein array of const daraus zu machen und an AddToVarRecArray zu übergeben.


jasocul - Fr 22.03.19 12:38

Ah, ok.
Das hatte ich bei der neuen Source-Variante übersehen.