Autor |
Beitrag |
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Mo 25.09.17 14:17
So, nach langer Zeit bin ich dann auch mal wieder mit einem Problem dabei. Ich schreibe derzeit ein Programm zur Dokumentenverwaltung, und habe dazu eine Klasse entwickelt, die eine Art Datenbank beinhaltet. In dieser Klasse sind auch eine Reihe von Records, zum Teil als Arrays, definiert. Normalerweise haben solche Arrays und Records ja eine eigene Speicherverwaltung, durch die Einbindung in die Klasse scheine ich diese Speicherverwaltung unabsichtlich auszuhebeln. Ganz konkret geht es mir um folgendes:
Unit Database
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:
| type TDatenbank = class public type TKlient = record Status : integer; Index : integer; Vorname : string; Nachname : string; Strasse : string; Ort : string; end; PKlient = ^TKlient; TKlientliste = record Count : integer; Liste : array of TKlient; end; PKlientliste = ^TKlientliste; constructor create(Path: String); function LastOperation: integer; private procedure LoadDB; procedure StoreValue(Klient: PKlient); overload; var iKlientenliste : TKlientliste; pKlientenliste : PKlientliste; Error : integer; Vorlagenpfad : string; end;
constructor TDatenbank.create(Path: String); begin Vorlagenpfad := Path; LoadDB; end;
procedure TDatenbank.LoadDB; new(PKlientenliste); PKlientenliste := @iKlientenliste; iKlientenliste.Count := 0; end;
procedure TDatenbank.NewKlient(Klient: PKlient); begin StoreValue(Klient); end;
procedure TDatenbank.StoreValue(Klient: PKlient); begin iKlientenliste.Liste[iKlientenliste.Count+1] := Klient^; inc(iKlientenliste.Count); end; |
Unit NewClient
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| procedure TForm2.Button1Click(Sender: TObject); var Klient : TDatenbank.TKlient; AKlient : TDatenbank.PKlient;
begin
With Klient do begin Status := 1; Index := 1; Vorname := Edit1.Text; Nachname := Edit2.Text; Strasse := Edit3.Text; Ort := Edit4.Text; end; New(AKlient); AKlient := @Klient; Datenbank.NewKlient(AKlient); Dispose(AKlient); end; |
In der Main habe ich die Klasse selbstverständlich instanziiert mit
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| var Datenbank: TDatenbank; Error : integer;
begin Dtenbank := TDatenbank.create(''); Error := Datenbank.LastOperation; if Error <> 0 then case error of 1: showmessage('Datenbank ist fehlerhaft, bitte Datenbanküberprüfung durchführen!'); 2: showmessage('Es existiert keine Datenbank, diese muß neu angelegt werden!'); end; end; end; |
Mein Problem ist der Zugriff auf Strukturen innerhalb der Datenbank (iKlientenliste) z. B. Dort wirft das Programm entweder Zugriffsverletzungen oder, wenn ich mit ReallocMem arbeite, Fehler bei der Bereichsprüfung. Wie kann ich diese Strukturen richtig initialisieren und mit Daten füttern? Die Zeiger sind ja (hoffentlich) richtig geetzt, ich vermute das Problem bei der Speicherverwaltung und wäre für Hilfe sehr dankbar.
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mo 25.09.17 15:21
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Mo 25.09.17 15:27
Frühlingsrolle hat folgendes geschrieben : | Guten Tag drstar,
ich tippe mal, es liegt an dem dynamischen Array:
Delphi-Quelltext 1: 2: 3: 4:
| TKlientliste = record Count: integer; Liste: array of TKlient; end; |
Die Problemzeile:
Delphi-Quelltext 1:
| Datenbank.NewKlient(AKlient); |
ruft StoreValue auf:
Delphi-Quelltext 1: 2: 3: 4: 5:
| procedure TDatenbank.StoreValue(Klient: PKlient); begin iKlientenliste.Liste[iKlientenliste.Count+1] := Klient^; inc(iKlientenliste.Count); end; |
Dabei wird nirgends eine Länge für das dynamische Array mit SetLength() festgelegt. |
Richtig, die gekapselte Routine StoreValue wird aufgerufen (soll auch so sein), da die Bearbeitung der Datenbank ausschließlich innerhalb der Datenbank geschehen soll, und somit lediglich die gefilterten Datensätze ausgespuckt werden sollen. Da spielt auch eine umfangreiche Fehlerbehandlung der Datenbank eine Rolle, die ich natürlich intern haben will.
Ich müßte also das Array anfangs mit SetLength(0) auf 0 festlegen, um es dann immer, wenn ich einen Wert hinzufüge, um eins zu erhöhen?
Edit:
Hab es jetzt wie folgt bearbeitet:
Unit Database
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:
| type TDatenbank = class public type TKlient = record Status : integer; Index : integer; Vorname : string; Nachname : string; Strasse : string; Ort : string; end; PKlient = ^TKlient; TKlientliste = record Count : integer; Liste : array of TKlient; end; PKlientliste = ^TKlientliste; constructor create(Path: String); function LastOperation: integer; private procedure LoadDB; procedure StoreValue(Klient: PKlient); overload; var iKlientenliste : TKlientliste; pKlientenliste : PKlientliste; Error : integer; Vorlagenpfad : string; end;
constructor TDatenbank.create(Path: String); begin Vorlagenpfad := Path; LoadDB; end;
procedure TDatenbank.LoadDB; new(PKlientenliste); PKlientenliste := @iKlientenliste; iKlientenliste.Count := 0; setLength(iKlientenliste.Liste, 0); end;
procedure TDatenbank.NewKlient(Klient: PKlient); begin StoreValue(Klient); end;
procedure TDatenbank.StoreValue(Klient: PKlient); begin setLength(iKlientenliste.Liste, iKlientenliste.Count+1); iKlientenliste.Liste[iKlientenliste.Count+1] := Klient^; inc(iKlientenliste.Count); end; |
Unit NewClient
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| procedure TForm2.Button1Click(Sender: TObject); var Klient : TDatenbank.TKlient; AKlient : TDatenbank.PKlient;
begin
With Klient do begin Status := 1; Index := 1; Vorname := Edit1.Text; Nachname := Edit2.Text; Strasse := Edit3.Text; Ort := Edit4.Text; end; New(AKlient); AKlient := @Klient; Datenbank.NewKlient(AKlient); Dispose(AKlient); end; |
In der Main habe ich die Klasse selbstverständlich instanziiert mit
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| var Datenbank: TDatenbank; Error : integer;
begin Dtenbank := TDatenbank.create(''); Error := Datenbank.LastOperation; if Error <> 0 then case error of 1: showmessage('Datenbank ist fehlerhaft, bitte Datenbanküberprüfung durchführen!'); 2: showmessage('Es existiert keine Datenbank, diese muß neu angelegt werden!'); end; end; end; |
Das Problem bleibt bestehen.
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mo 25.09.17 15:41
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Mo 25.09.17 15:49
Frühlingsrolle hat folgendes geschrieben : | Deine Klientenliste hat doch ein Count-Attribut. Du brauchst einzig danach die Array-Länge auszurichten. Count sollte von Anfang an mit 0 initialisiert werden und mit jeden neuen Klienten erhöht oder beim Löschen, vermindert werden. Im Anschluss setzt du erneut die Array-Länge auf Count und das wars. |
Ist doch bereits implementiert - der Fehler bleibt aber (Fehler bei Bereichsprüfung), siehe meine Antwort zuvor, dort habe ich genau das eingefügt.
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mo 25.09.17 15:57
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mo 25.09.17 16:13
Eigentlich ist das ganze Design der Klasse... sagen wir gewöhnungsbedürftig.
Heutzutage ist TKlient ebenfalls eine Klasse. Und das Array, das so viele Probleme macht, ist entweder eine TObjectList (bis D2009) oder eine TObjectList<TKlient>.
Sobald man diese ObjectList hat, entfällt das komplette Geraffel mit Speicher holen und freigeben und verwalten. Macht alles die ObjectList.
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:
| type TKlient = class(TObject) public Status : integer; Index : integer; Vorname : string; Nachname : string; Strasse : string; Ort : string; end;
type TDatenbank = class public Klientliste: TObjectList<TKlient>;
constructor create(Path: String); function LastOperation: integer; private procedure LoadDB; procedure StoreValue(Klient: TKlient); overload; Error : integer; Vorlagenpfad : string; end;
constructor TDatenbank.Create(Path: string) begin KlientListe:=TObjectList<TKlient>.Create(true); ... end; |
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Mo 25.09.17 16:37
OlafSt hat folgendes geschrieben : | Eigentlich ist das ganze Design der Klasse... sagen wir gewöhnungsbedürftig.
Heutzutage ist TKlient ebenfalls eine Klasse. Und das Array, das so viele Probleme macht, ist entweder eine TObjectList (bis D2009) oder eine TObjectList<TKlient>.
Sobald man diese ObjectList hat, entfällt das komplette Geraffel mit Speicher holen und freigeben und verwalten. Macht alles die ObjectList.
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:
| type TKlient = class(TObject) public Status : integer; Index : integer; Vorname : string; Nachname : string; Strasse : string; Ort : string; end;
type TDatenbank = class public Klientliste: TObjectList<TKlient>;
constructor create(Path: String); function LastOperation: integer; private procedure LoadDB; procedure StoreValue(Klient: TKlient); overload; Error : integer; Vorlagenpfad : string; end;
constructor TDatenbank.Create(Path: string) begin KlientListe:=TObjectList<TKlient>.Create(true); ... end; | |
Soweit ich das verstanden habe, ist dann jeder Datensatz ein eigenes Objekt - ich sehe nicht, inwiefern mir das alles einfacher machen soll. Nicht nur, daß ich alles umschreiben müßte, ich müßte mir dann erstmal Gedanken machen, wie ich es anstelle, meine Datensätze als Objekte zu behandeln.
Dennoch verstehe ich nicht, weshalb es zu diesem Fehler kommt. In meiner ersten Antwort (dritter Beitrag dieses Threads) habe ich bei der Initialisierung nicht nur Count auf 0 gesetzt (hatte ich vorher schon), sondern eben auch SetLength auf 0 gesetzt. StoreValue hat ihn dann um 1 erhöht (SetLength := Count+1). Funktioniert auch nicht - irgendwo muß ich also einen Denkfehler haben. Bevor ich jetzt anfange, alles auf TObjectList umzuschreiben, würde ich lieber gern den Fehler ausmerzen - sollte das dann halt nicht gelingen, werde ich mich intensiver mit TObjectList beschäftigen.
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mo 25.09.17 16:51
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Mo 25.09.17 16:57
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mo 25.09.17 17:07
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Mo 25.09.17 17:17
Frühlingsrolle hat folgendes geschrieben : | 1.) Wo wird:
iKlientenliste.Count := 0; zugwiesen? Würde ich z.B. im Konstruktor der Klasse erwarten.
2.) Hast du mein Beispiel hier übersehen? |
Die Zuweisung erfolgt in der Routine TDatenbank.DBLoad, dort zu finden, ich hatte dort auch geschrieben, daß ich problemlos Count auf 0 setzen konnte ohne Fehler.
Die Erhöhung von Count sowie das SetLength von iKlientenliste erfolgt unter StoreValue, da dort auch die Zuweisung und Speicherung des Datensatzes erfolgt. Übersehen habe ich es demnach nicht.
|
|
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Do 28.09.17 15:52
So, nun habe ich versucht, es mit TObjectList zu probieren und falle gleich mal auf's Maul.
[dcc32 Fehler] Database.pas(62): E2003 Undeklarierter Bezeichner: 'TObjectList<>'
Fehlt mir eine Unit in der Uses-Klausel oder was muß ich sonst anpassen? Habe Delphi 10.1, falls das relevant ist.
Edit:
Hat sich erledigt, System.Generics.Collections ist die benötigte Unit.
|
|
drstar
Beiträge: 79
Erhaltene Danke: 2
Windows 8.1/x64
Delphi 10.1
|
Verfasst: Do 28.09.17 18:33
So, ich hab jetzt auf TObjectList umgestellt. Funktioniert eigentlich auch alles, nur ein Problem habe ich: TObjectList.count fängt bei 0 an - wie ermittle ich, ob überhaupt schon Objekte alias Datensätze in der Liste drin sind? Count auf 0 prüfen scheidet ja aus, denn 0 ist bereits ein Objekt. Wie stelle ich fest, ob schon Objekte gespeichert sind, bevor ich versuche, auf Objekte in der Liste zuzugreifen, die womöglich nicht existieren?
|
|
mandras
Beiträge: 430
Erhaltene Danke: 107
Win 10
Delphi 6 Prof, Delphi 10.4 Prof
|
Verfasst: Do 28.09.17 20:37
Hallo drstar,
Count kannst/mußt du nehmen.
Wenn die Liste leer ist ist Count 0.
Wenn 3 Einträge in der Liste sind so sind dies die Einträge 0,1 und 2
|
|
gerd8888
Beiträge: 205
Erhaltene Danke: 3
Win7
Delphi 10.1 Starter (kostenlos) Lazarus
|
Verfasst: Do 28.09.17 20:50
Hallo drstar,
Zitat: |
Count auf 0 prüfen scheidet ja aus, denn 0 ist bereits ein Objekt. Wie stelle ich fest, ob schon Objekte gespeichert sind |
[/quote]
ich würde das count mit NIL prüfen. Das ist dann der Wert bei Count -1 !
Zuletzt bearbeitet von gerd8888 am Do 28.09.17 21:12, insgesamt 1-mal bearbeitet
|
|
mandras
Beiträge: 430
Erhaltene Danke: 107
Win 10
Delphi 6 Prof, Delphi 10.4 Prof
|
Verfasst: Do 28.09.17 21:10
> ich würde das count mit NIL prüfen.
TObjectList.count ist ein Integer - also muß auf 0 geprüft werden
|
|
gerd8888
Beiträge: 205
Erhaltene Danke: 3
Win7
Delphi 10.1 Starter (kostenlos) Lazarus
|
Verfasst: Do 28.09.17 21:18
Hallo mandras,
soweit ich weiss, ist es dann bei dynamischen array (count) -1 entspricht NIL.
Ich bin mir jetzt auch nicht mehr ganz sicher. Normalerweise nimmt man NIL her. Das geht 100 prozentig.
|
|
mandras
Beiträge: 430
Erhaltene Danke: 107
Win 10
Delphi 6 Prof, Delphi 10.4 Prof
|
Verfasst: Do 28.09.17 22:32
drstar schrieb daß nun ein Objekt vom Typ TObjectList verwendet wird.
Hier wird per Count:integer festgehalten wie viele Elemente daran sind
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Fr 29.09.17 01:25
gerd888 erzählt Unsinn. Wenn die TObjectList<> nicht initialisiert ist, ist die ganze ObjectList Nil. Ein Zugriff auf das Count-Property gibt dann auch prompt eine Exception.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| var TOL: TObjectList<TSomething>; begin if Assigned(TOL) then begin if TOL.Count > 0 then begin ... end; end; end; |
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|