Autor Beitrag
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: 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 :roll:

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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Sa 21.08.10 19:11 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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.

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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:
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Sa 21.08.10 19:18 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Sa 21.08.10 19:23 
user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var
  AutoFree: IAutoFree;
begin
  // ...
  ResultSet := ...;
  AutoFree := TAutoFree.Create(ResultSet)

  // work with ResultSet, is freed at the end of Method

end;

Für diesen Beitrag haben gedankt: dummzeuch, Martok
dummzeuch
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 593
Erhaltene Danke: 5


Delphi 5 ent, Delphi 6 bis Delphi XE8 pro
BeitragVerfasst: Sa 21.08.10 20:46 
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:

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:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
type
  TMyObj = object
    // .... hier weiter fast wie bei einer Klasse
  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:
ausblenden 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;
  // irgendwas mit dem Ergebnis in Res.FResult machen
end;
// hier endet die Gueltigkeit des Records Res und damit die
// des darin gespeicherten FAutoFree Interfaces, welches damit
// automatisch FResult freigibt.

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:

ausblenden volle Höhe 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:
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
    // Datenfelder hier
  public
    constructor Init;
    destructor Done; virtual// ja, virtual, nicht override
    // Zugriffsmethoden hier
  end;

constructor TAutoObj.Init;
begin
  inherited Init;
  FAutoFree := TAutoFree.Create(@Self);
end;

destructor TAutoObj.Done;
begin
  // nix tun, aber virtual, damit er von TAutoFree aufgerufen werden kann
  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
  // wichtig: Inherited Init aufrufen, sonst
  // wird FAutoFree nicht initialisiert!
  inherited Init;
end;

destructor TDatenObj.Done;
begin
  // irgendwelche Aufraeumarbeiten fuer die Felder
  inherited Done;
end;

// sonstige Methoden von TDatenObj

function EineDatenbankabfrage: TDatenObj;
begin
  Result.Init; // hier wird das FAutoFree Inteface erzeugt
  Result.Datenfeld1 := Datenbankwert;
  // usw.
end;

var
  Res: TDatenObj;
begin
  Res := EineDatenbankabfrage;
  // irgendwas mit dem Ergebnis in Res machen
end;
// hier endet die Gueltigkeit des Objeks Res (welches auf dem Stack liegt)
// und damit die des darin gespeicherten FAutoFree Interfaces, welches damit
// automatisch Res freigibt.


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.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  Obj: TDatenObj;
begin
  Obj.Init; // dies ist keine Objektreferenz sondern ein Objekt auf dem Stack!
  // ...
  FreeAndNil(Obj); // dies fuehrt nicht zu einem Compile Fehler aber
  // vermutlich zu einer Access Violation beim Ausfuehren.
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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: 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.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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.

user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
Bzgl. TSQLiteTable, von welcher VCL/RTL Klasse leitet diese ab (TDataSet?)

Gar nicht. Ist nur ein Resultset, ohne alles.

user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
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...
user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
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.

user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
dann kannst du auch eine TAutoFree Klasse machen, die ein IAutoFree implementiert.

Auch ein interessantes Konzept. Entspricht ja fast dem mit .Value was user profile iconjaenicke oben nannte, nur generischer. Aber auch wieder mit Mehraufwand im Usercode.

user profile icondummzeuch hat folgendes geschrieben Zum zitierten Posting springen:

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 :roll:

_________________
"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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Sa 21.08.10 21:04 
user profile icondummzeuch hat folgendes geschrieben Zum zitierten Posting springen:
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.