Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Records in dyn. Array einfügen und löschen


Master_of_Magic - Mo 17.04.06 15:31
Titel: Records in dyn. Array einfügen und löschen
Ich bin gerade dabei mir eine kleine Datenbankanwendung zu basteln ('Datenbank' ist etwas übertrieben :wink: ). Die Datensätze bestehen dabei aus dem Record:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
type
  TUser = record
    id:word;
    name:string;
    gala:byte;
    mail:string;
    pdate:word;
    paccount:byte;
    pamount:word;
    state:byte;
    hddsn:cardinal;
    winsn:cardinal;
    lansn:cardinal;
    cpusn:cardinal;
    comment:string;
  end;

var
  user: array of TUser;


Die Daten werden in den Stringgrid db dargestellt. Um einen Datensatz zu löschen hab ich mir folgende Prozedur gebastelt (Teile im Forum gefunden):


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure TForm1.deleteClick(Sender: TObject);
var index,i:integer;
begin
  index:=db.Selection.Top-1;

  Finalize(user[index]);
  Move(user[index + 1], user[index], SizeOf(TUser) * (High(user) - index));
  FillChar(user[High(user)], SizeOf(TUser), 0);
  SetLength(user, High(user));

  db.Row := index+1;
  if (db.Row = db.RowCount -1then
    db.RowCount := db.RowCount - 1
  else
    begin
      for i := index+1 to db.RowCount-1 do db.Rows[i] := db.Rows[i+1];
      db.RowCount := db.RowCount - 1;
    end;

  displayrecord(index);
end;


Das funktioniert soweit - trotzdem bleiben stellen sich mir 3 Fragen:
1. Welche Bedeutung hat Finalize in diesem Zusammenhang?
2. Warum kann ich die Daten einfach mit move verschieben, obwohl der Record dynamische(!) Strings enthält?
3. Weshalb liefert SizeOf(TUser) immer den Wert 44 und wie setzt sich dieser zusammen?

Nun zu meinem eigentlichen Problem.
Zum Einfügen eines neuen Datensatzes verwende ich folgende Prozedur:


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.insertClick(Sender: TObject);
var index,i:integer;
begin
  index:=db.Selection.Top-1;

  SetLength(user, Length(user)+1);
  Move(user[index], user[index+1], SizeOf(TUser) * (High(user) - index));

  with user[index] do
  begin
    if index=0 then id:=1
    else id:=user[index-1].id;
    name:='';
    gala:=0;
    mail:='';
    pdate:=38353;
    pamount:=0;
    paccount:=0;
    state:=5;
    hddsn:=0;
    winsn:=0;
    lansn:=0;
    cpusn:=0;
    comment:='';
  end;

  displaydbentry(index+1,index);
  displayrecord(index);

  db.RowCount:=db.RowCount+1;
  for i := db.RowCount-2 downto index+1 do
    db.Rows[i+1] := db.Rows[i];
end;


Da scheint jetzt irgendein Fehler (ich tipp im move-Teil) zu sein, da nachfolgende Datensätze (bzw. deren Strings) zerhackstückt werden. Außerdem erhalte ich beim Speichern des user-Arrays eine Zugriffsverletzung ...

Weiß einer, woran das liegt?


Master_of_Magic - Sa 22.04.06 15:57

hmm, da anscheinend niemand eine Antwort auf meine Frage(n) weiß, will ich sie mal umformulieren:

Kann mir einer sagen, wie ich Record-Elemente in ein (dyn.) Array einfügen bzw. zwei Records vertauschen kann?

Der obige Code ist nur mein bisheriges Ergebnis - der muss also nicht beibehalten werden ... :wink:


alias5000 - Sa 22.04.06 16:14

Vertauschen (allgemein formuliert):

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Procedure Exchange(index1, index2: integer);
var Rec: TMyRecord;
begin
Rec := RecArray[index1];
RecArray[index1] := RecArray[index2];
RecArray[index2] := Rec;
end;


Hilft das weiter?


Master_of_Magic - So 23.04.06 14:52

Ok, auf das Vertauschen hätt ich auch selber kommen können - ist ja quasi BubbleSort ...

Aber kann ich einfach so zwei Datensätze vertauschen? Weil die haben ja durch den String eine verschiedene Größe, oder?


Horst_H - So 23.04.06 15:14

Hallo,

der String ist ja ein Zeiger auf eine String-Struktur, deshalb klappt es.
Die Strings selber stehen also nicht im record sondern woanders.
Diese werden also nie verschoben, wenn Du dein array user vergroesserst oder verkleinerst.

Beim nach oben schieben muesste:
Move(Von,Nach,Anzahl)

Delphi-Quelltext
1:
Move(user[index + 1], user[index], SizeOf(TUser) * (High(user) - index));                    

wohl

Delphi-Quelltext
1:
Move(user[index], user[index+1], SizeOf(TUser) * (High(user) - index));                    

sein.
Aber bei Dir haette trotzdem kein Muell auftreten duerfen, sondern der letzt Datensatz waer doppelt vorhanden und der an der Position Index einfach ueberschrieben.

Gruss Horst


Master_of_Magic - Mo 24.04.06 18:34

@Horst_H
Das heißt also, ich kann meine Records beliebig verschieben und vertauschen, da die Daten selbst nicht verändert werden, oder? Das macht die Sach einfacher ... *freu*

Deine Code-Schnipsel blick ich nicht ganz: was meinst du mit 'nach oben schieben'? Meine zwei Code-Teile oben sind nur zum löschen und einfügen eines Records:
Beim Löschen werden die user[index+1] nach user[index] verschoben und beim einfügen andersrum - von daher stimmt das doch.

Was mein Problem angeht: Ich habe herausgefunden, dass das Array passt, jedoch nur die Anzeige Müll liefert. Liegt von an den letzten Zeilen der Procedure, die falsch herum waren. Meine Procedure sieht nun so aus:


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:
procedure TForm1.insertClick(Sender: TObject);
var index,i:integer;
begin
  index:=db.Selection.Top-1;

  SetLength(user, Length(user)+1);
  {For i:=High(user)-1 downto index do
  begin
  user[i+1]:=user[i];
  end;}


  Move(user[index], user[index+1], SizeOf(TUser) * (High(user) - index));

  with user[index] do
  begin
    if index=0 then id:=1
    else id:=user[index-1].id;
    name:='Name';
    gala:=0;
    mail:='Mail';
    pdate:=38353;
    pamount:=0;
    paccount:=0;
    state:=5;
    hddsn:=0;
    winsn:=0;
    lansn:=0;
    cpusn:=0;
    comment:='';
  end;

  db.RowCount:=db.RowCount+1;
  for i := db.RowCount-2 downto index+1 do
    db.Rows[i+1] := db.Rows[i];

  displaydbentry(index+1,index);  //die beiden Aufrufe waren anfangs vor der Schleife...
  displayrecord(index);

end;


Nun passt die Anzeige und das Einfügen klappt auch - meistens. Manchmal (v.a. beim Beenden) erhalte ich jedoch eine 'Invalid Pointer Operation', eine 'AccessViolation' oder in seltenen Fällen sogar eine Zugriffsverletzung auf die Kernel.dll ... :shock:

Verwende ich jedoch eine For Schleife (die die im Code oben auskommentiert ist) klappt alles wunderbar ohne Fehler.

Im Prinzip ist mein Problem damit gelöst, jedoch wurde ich doch gerne die Ursache der move-Fehler wissen ... kann mir da einer helfen?


Horst_H - Mo 24.04.06 19:29

Hallo,
Du bewegst ein record zu wenig (auch beim Loeschen) also,

Delphi-Quelltext
1:
Move(user[index], user[index+1], SizeOf(TUser) * (High(user) - index +1));                    

denn 1..10 nach 2..11 verschieben sind 10 Elemente aber 10-1=9.
Deshalb hat deine Variable Comment (=string= Zeiger auf eine Delphi-StringStruktur) eine Zuordnung in die unendlichen Weiten des Hauptspeichers(, in die noch nie ein Bit vorgedrungen ist, oder total verbotene Bereiche access violation).
Dies kann also zu Mist fuehren.

Gruss Horst


Master_of_Magic - Di 25.04.06 18:58

Nein, das hast du was vergessen:
Bevor ich move anwende, wird ja das Array bereits um 1 vergrößert (beim
Einfügen)! High(user) liefert also bereits 11 zurück (um bei deinem Beispiel zu bleiben). Somit passt das wieder (11-1=10 Elemente zu verschieben).

Dein Quellcode liefert daher eine Zugriffsverletzung (bzw. eine 'Debugger Fault Notification '...)