| Autor |
Beitrag |
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Sa 21.08.10 18:46
Hallo!
ich bin grade schön am basteln mit SQLite. Dieser Wrapper gibts ResultSets als separate Objekte zurück. Die muss man dann freigeben, wenn man fertig ist.
Da ich von PHP da etwas verwöhnt bin, hätte ich gerne auch RefCounting um mir das try/finally zu sparen. Außerdem musste ich ich grade feststellen dass ich das an fast allen Stellen vergessen habe
Der einfache Ansatz wäre, alle Funktionen des Resultsets in einem Interface zu deklarieren und dann von TInterfacedObject zu erben. Jede Variablendeklaration erfolgt dann nur noch als ISQLiteTable.
Heißt aber Code Duplication, da ich ja 2 identische Deklarationen rumstehen habe dann. Die würde ich gerne vermeiden.
Außerdem brauche ich ja eigentlich keine wirkliche Interface-Funktionalität, nur das RefCounting.
Gibt es da irgendwelche anderen Möglichkeiten mit vertretbarem Aufwand?
Danke schonmal,
Sebastian
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
jaenicke
      
Beiträge: 19339
Erhaltene Danke: 1752
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Sa 21.08.10 18:59
Für eine Referenzzählung brauchst du die Unterstützung von Delphi, denn nur dafür wird automatisch Code erzeugt, der _Release aufruft. Und das gibt es eben nur mit Interfaces.
Die mehrfache Deklaration kannst du dir sparen. Entweder sprichst du alles über eine indizierte Eigenschaft an (wie Values bei TStringList), oder du baust in das Interface einfach eine Eigenschaft Value ein, die ein eigenes Objekt ist. Und darin steht dann alles. Dann kannst du alles dort einmal deklarieren, musst dafür aber im Code immer Value dazuschreiben.
Grundsätzlich bringt eine solche Referenzzählung aber auch Probleme mit sich, z.B. wenn Zugriffe erfolgen obwohl keine Referenz mehr auf das Interface existiert. Du musst da sehr drauf aufpassen, dass du da nichts falsch machst. Sonst hast du hinterher schwer zu findende Speicherprobleme.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Sa 21.08.10 19:00
Leider nicht wirklich, da das RefCounting der Interfaces über die Compiler-Magic läuft. Daher bleibt eigentlich nur Geschenkpapier für deine Objekte.
Was man probieren könnte, wäre das automatische Generieren eines Wrappers @Runtime via Reflection, dann ist deine Kompatibilität für alte Delphi-Versionen aber sofort hinüber ...
_________________ 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.
|
|
Martok 
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Sa 21.08.10 19:11
jaenicke hat folgendes geschrieben : | | oder du baust in das Interface einfach eine Eigenschaft Value ein, die ein eigenes Objekt ist. Und darin steht dann alles. Dann kannst du alles dort einmal deklarieren, musst dafür aber im Code immer Value dazuschreiben. |
Klingt spaßig, ist ja dann aber fast noch mehr Aufwand als ordentliche Resourcenschutzblöcke.
jaenicke hat folgendes geschrieben : | | Grundsätzlich bringt eine solche Referenzzählung aber auch Probleme mit sich, z.B. wenn Zugriffe erfolgen obwohl keine Referenz mehr auf das Interface existiert. Du musst da sehr drauf aufpassen, dass du da nichts falsch machst. Sonst hast du hinterher schwer zu findende Speicherprobleme. |
Das war auch einer der Gründe für das Thema hier.
Theoretisch kann da nichts passieren: Die Daten kopiere ich sowieso, und ansonsten brauche ich die Referenz eben nur einmal kurz während der Iteration zum Ausgeben.
BenBE hat folgendes geschrieben : | | Was man probieren könnte, wäre das automatische Generieren eines Wrappers @Runtime via Reflection, dann ist deine Kompatibilität für alte Delphi-Versionen aber sofort hinüber ... |
Hätt ja sein können, dass jemand sowas schonmal fertig gebaut hat.
In letzter Zeit mite ich ja immer öfter feststellen, dass es für Delphi einige ziemlich geniale Hacks gibt, die Features nachrüsten die man nicht für möglich gehalten hätte.
EDIT: oh, und interface versteht keine constructoren. hm.
EDIT2: und properties werden auch hässlich. Da hab ich nicht mal dran gedacht.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
Zuletzt bearbeitet von Martok am Sa 21.08.10 19:14, insgesamt 2-mal bearbeitet
|
|
jaenicke
      
Beiträge: 19339
Erhaltene Danke: 1752
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Sa 21.08.10 19:11
Mir fällt aber noch etwas anderes ein:
Du könntest dir alle Ergebnisobjekte in einer Liste merken und wenn eines davon wieder freigegeben wird automatisch die Liste informieren. Dann hast du am Ende in der Liste die noch nicht freigegebenen, die vergessen wurden.
Dann kannst du die einfach selbst freigeben und am besten mit OutputDebugString oder so auch den Entwickler informieren, dass da noch was fehlte.
// EDIT:
Martok hat folgendes geschrieben : | | Klingt spaßig, ist ja dann aber fast noch mehr Aufwand als ordentliche Resourcenschutzblöcke. |
Nicht nur das, mir fällt gerade ein, dass jemand auf die Idee kommen könnte, sich das Objekt zu merken, das aber dann ggf. das Interface bereits aufgeräumt hat. Also keine gute Idee.
Zuletzt bearbeitet von jaenicke am Sa 21.08.10 19:18, insgesamt 1-mal bearbeitet
|
|
Martok 
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Sa 21.08.10 19:18
jaenicke hat folgendes geschrieben : | Dann hast du am Ende in der Liste die noch nicht freigegebenen, die vergessen wurden.
Dann kannst du die einfach selbst freigeben und am besten mit OutputDebugString oder so auch den Entwickler informieren, dass da noch was fehlte. |
Sounds like a plan.
Da die Datenbank eh eine Factory ist macht sich das sogar recht einfach: es gibt schon jemanden, der berechtigt ist die Referenzliste zu halten.
Werde ich später mal bauen und testen.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
jaenicke
      
Beiträge: 19339
Erhaltene Danke: 1752
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Sa 21.08.10 19:19
Wegen Konstruktoren und Properties:
Ein Interface hat keine Konstruktoren, weil es die ja auch nicht braucht. Das Interface bzw. der erzeugte Code dafür ruft ja den Destruktor des dahinterliegenden Objektes automatisch auf. Und erzeugen tust du ja nicht das Interface sondern ein Objekt der Klasse, die dieses implementiert.
Properties gehen im Interface ganz normal, du musst halt immer einen Getter und ggf. Setter machen.
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: Sa 21.08.10 19:21
Wenn du Memoryleaks suchst, kannst du auch einfach ReportMemoryLeaksOnShutdown auf True stellen.
Bzgl. TSQLiteTable, von welcher VCL/RTL Klasse leitet diese ab (TDataSet?), und erzeugst du die selbst oder ist das Fremdcode? Interfaces unterstützen auch Properties und eine doppelte Deklaration ist bei Interfaces ja eigentlich üblich.
|
|
jaenicke
      
Beiträge: 19339
Erhaltene Danke: 1752
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Sa 21.08.10 19:23
delfiphan hat folgendes geschrieben : | | Wenn du Memoryleaks suchst, kannst du auch einfach ReportMemoryLeaksOnShutdown auf True stellen. |
Den Komfort gab es bei der Delphiversion, die er im Profil fettgeschrieben hat (Delphi 7) noch nicht. Bei Delphi 2006 / Turbo Delphi dann natürlich schon.
// EDIT:
FastMM um das manuell zu haben, gab es natürlich auch schon für Delphi 7, es war nur noch nicht integriert.
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: Sa 21.08.10 19:44
Du sagst "das try/finally". Wenn das eh nur eine relativ lokale Stelle ist, dann kannst du auch eine TAutoFree Klasse machen, die ein IAutoFree implementiert. TAutoFree würde im Konstruktor ein TObject entgegennehmen und diese im Destruktor freigeben.
Also sowas wie:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| var AutoFree: IAutoFree; begin ResultSet := ...; AutoFree := TAutoFree.Create(ResultSet)
end; |
Für diesen Beitrag haben gedankt: dummzeuch, Martok
|
|
dummzeuch
      
Beiträge: 593
Erhaltene Danke: 5
Delphi 5 ent, Delphi 6 bis Delphi XE8 pro
|
Verfasst: Sa 21.08.10 20:46
Martok hat folgendes geschrieben : |
Der einfache Ansatz wäre, alle Funktionen des Resultsets in einem Interface zu deklarieren und dann von TInterfacedObject zu erben. Jede Variablendeklaration erfolgt dann nur noch als ISQLiteTable.
Heißt aber Code Duplication, da ich ja 2 identische Deklarationen rumstehen habe dann. Die würde ich gerne vermeiden.
Außerdem brauche ich ja eigentlich keine wirkliche Interface-Funktionalität, nur das RefCounting.
Gibt es da irgendwelche anderen Möglichkeiten mit vertretbarem Aufwand?
|
Wie delfiphan schon schrieb: Eine TAutoFree Klasse koennte evtl. helfen, aber sie loest nicht wirklich Dein Problem, da Du dann statt try...finally darauf achten musst, TAutoFree zu benutzen. Das ist reine Kosmetik.
Eine moegliche Loesung waere, dass Du statt einer Klasse ein Record zuruecklieferst. Damit schraenkst Du Dich aber wieder ziemlich ein, denn Records koennen erst ab Delphi 2007 Methoden und Properties haben und selbst dann noch keine Vererbung.
Du koenntest Objekte nach dem "alten" Objektmodell von Borland Pascal verwenden:
Delphi-Quelltext 1: 2: 3: 4:
| type TMyObj = object end; |
Die koennen auf dem Stack liegen und geben dann den von ihnen belegten Speicher automatisch frei. Aber auch das wird knallen, sobald eines der Objekte einen Destruktor benoetigt, denn der wird nicht automatisch aufgerufen.
Evtl. koennte man TAutoFree und Records kombinieren:
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:
| type TResRecord = record FResult: TDatenklasse; FAutoFree: IAutoFree; end;
function EineDatenbankabfrage: TResRecord; begin Result.FResult := ... irgendwie zuweisen
Result.FAutoFree := TAutoFree.Create(Result.FResRecord); end;
var Res: TResRecord; begin Res := EineDatenbankabfrage; end; |
Das ist insofern etwas eleganter, dass sich der Aufrufer nicht um die Freigabe der Daten kuemmern muss, das erledigt die Funktion, die sie liefert. Das erkauft man sich damit, dass man immer eine zusaetzliche Dereferenzierung auf FResult machen muss. Nicht schoen, aber es wuerde funktionieren.
(Hm, man koennte noch einen Schritt weitergehen und TAutoFree mit "alten" Objekten kombinieren, also einfach statt eines Records das einen Pointer auf die Datenklasse und einen auf IAutofree speichert, ein Object nehmen, das den IAutoFree Pointer selbst enthaelt:
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: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84:
| type IAutoFree = IInterface;
PAutoObj = ^TAutoObj; TAutoObj = object private FAutoFree: IAutoFree; public constructor Init; destructor Done; virtual; end;
TObjAutofree = class(TInterfacedObject, IAutoFree) private FObj: PAutoObj; public constructor Create(_Obj: PAutoObj); destructor Destroy; override; end;
TDatenObj = object(TAutoObj) private public constructor Init; destructor Done; virtual; end;
constructor TAutoObj.Init; begin inherited Init; FAutoFree := TAutoFree.Create(@Self); end;
destructor TAutoObj.Done; begin inherited Done; end;
constructor TObjAutofree.Create(_Obj: PAutoObj); begin inherited Create; FObj := _Obj; end;
destructor TObjAutofree.Destroy; begin FObj^.Done; inherited; end;
constructor TDatenObj.Init; begin inherited Init; end;
destructor TDatenObj.Done; begin inherited Done; end;
function EineDatenbankabfrage: TDatenObj; begin Result.Init; Result.Datenfeld1 := Datenbankwert; end;
var Res: TDatenObj; begin Res := EineDatenbankabfrage; end; |
Das duerfte mit Delphi 7 funktionieren (mit Delphi 2007 wohl auch noch), aber das alte Objektmodell wurde schon damals nicht mehr offiziell von Borland unterstuetzt und ich weiss nicht inwieweit Embarcadero diesen alten Zopf noch weiter mit sich rumschleppen wird. Ausserdem muss man hoellisch aufpassen, dass man nicht neues und altes Objektmodell durcheinander wirft und z.B.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| var Obj: TDatenObj; begin Obj.Init; FreeAndNil(Obj); end; |
Und zu guter letzt: Kaum jemand kennt noch das alte Objektmodell (nur so alte Saecke wie ich). Wenn Du also damit irgendein Problem hast, duerfte es schwer werden, dafuer Hilfe zu bekommen.
Obigen Code habe ich nur so runtergetippt, ich uebernehme keine Garantie dafuer, dass er compiliert oder gar funktioniert.
twm
Zuletzt bearbeitet von dummzeuch am Sa 21.08.10 21:10, insgesamt 1-mal bearbeitet
|
|
Martok 
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Sa 21.08.10 20:57
Natürlich gibts FastMM schon
Aber wie ganz richtig gesagt krieg ich damit nur gesagt was ich vergessen hab.
jaenicke hat folgendes geschrieben : | | Properties gehen im Interface ganz normal, du musst halt immer einen Getter und ggf. Setter machen. |
Klar, müsste aber die drunterliegende Klasse umfassend anpassen. Und das muss nicht wirklich sein.
delfiphan hat folgendes geschrieben : | | Bzgl. TSQLiteTable, von welcher VCL/RTL Klasse leitet diese ab (TDataSet?) |
Gar nicht. Ist nur ein Resultset, ohne alles.
delfiphan hat folgendes geschrieben : | | und erzeugst du die selbst oder ist das Fremdcode? |
Fremdcode: GetTable (ein unglücklich benanntes ExecSQLAndReturnTheResultset) erzeugt das. Aber nur an einer Stelle, könnte man also leicht ändern...
delfiphan hat folgendes geschrieben : | | Wenn das eh nur eine relativ lokale Stelle ist |
... und damit hat das auch nur eine sehr kurze Lebensdauer. Nur beim Reporting ist das etwas länger, aber selbst da gibts eine aufrufende Prozedur, die während der ganzen Zeit nicht verlassen wird.
delfiphan hat folgendes geschrieben : | | dann kannst du auch eine TAutoFree Klasse machen, die ein IAutoFree implementiert. |
Auch ein interessantes Konzept. Entspricht ja fast dem mit .Value was jaenicke oben nannte, nur generischer. Aber auch wieder mit Mehraufwand im Usercode.
dummzeuch hat folgendes geschrieben : |
Evtl. koennte man TAutoFree und Records kombinieren [...] Nicht schoen, aber es wuerde funktionieren. |
Hui, geballte Kreativität hier. Ich kann gar nicht so schnell antworten
Auch das eine interessante aber irgendwie unschöne Idee.
Mir ist grad noch etwas eingefallen, dass man ja durchaus den Dereferenzierungs-Code selbst einfügen kann. Aber das ist dann ja NOCH komplizierter.
Listen für Notfälle und try/finally regulär klingt grade gar nicht mehr so schlecht 
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
jaenicke
      
Beiträge: 19339
Erhaltene Danke: 1752
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Sa 21.08.10 21:04
dummzeuch hat folgendes geschrieben : | | denn Records koennen erst ab Delphi 2007 Methoden und Properties haben |
Ab Delphi 2006 um genau zu sein, das Feature kam genau wie Klassenmethoden, nested types, for..in usw. in Delphi 2006 nachdem die neue IDE bei Delphi 2005 dran war.
|
|
|