![]() |
Ini-Writes global abfangen?
Für ein aktuelles Projekt wäre es nützlich, wenn ich alle Ini-Write-Befehle global abfangen könnte.
Am besten sollte das einfach in eins bestehendes Projekt einzuarbeiten sein (> 100.000 Zeilen). Ist das irgendwie ohne große Hexerei möglich? Edit: bzw.Ini.UpdateFile abzufangen wäre auch nützlich. |
AW: Ini-Writes global abfangen?
Spontan, ohne es getestet zu haben, wäre hier die einfachste Variante, die mir einfällt, einen globalen Classhelper für die TIniFile zu erstellen.
Delphi-Quellcode:
Das ganze müsste dann für jede Write-Variante gemacht werden.
TIniFileHelper = class helper for TIniFile
public procedure WriteString(const Section, Ident, Value: String); end; procedure TIniFileHelper.WriteString(const Section, Ident, Value: String); begin //hier reagieren inherited WriteString(Section, Ident, Value); end; Dazu sollte man sich etwas in die Classhelper einlesen: ![]() Alternativ leitest du die TIniFile-Klasse ab, und änderst alle Verwendungen. Vielleicht gibt es auch noch andere Varianten die keinen zu großen Aufwand bedeuten, die mir aber im Moment nicht einfallen. |
AW: Ini-Writes global abfangen?
Ich hätte auch direkt die Klassenhelfer genannt, aber du hast schneller getippt ;-)
|
AW: Ini-Writes global abfangen?
Die class-helper sind wirklich toll. Danke für den Hinweis.
Nur leider funktioniert das nich mit UpdateFile. Eine Fehlermeldung bekomme ich keine und eine Ini-Datei wird auch nicht geschrieben.
Delphi-Quellcode:
'X' wird angezeigt, UpdateFile scheinbar aber nicht ausgeführt (da Datei nicht erstellt wird)
TIniFileHelper = class helper for TCustomIniFile
public procedure UpdateFile; virtual; end; procedure TIniFileHelper.UpdateFile; begin ShowMessage('X'); inherited UpdateFile; end; procedure TForm1.Button2Click(Sender: TObject); var IniF: TCustomIniFile; begin IniF := TMemIniFile.Create('......'); try IniF.WriteString('section', 'ident', 'value'); finally IniF.UpdateFile; IniF.Free; end; end; |
AW: Ini-Writes global abfangen?
Delphi-Quellcode:
Spontan hätte ich jetzt das "virtual" im Classhelper als schuldigen in verdacht. Nimm das einfach mal weg, dann sollte es laufen.
TIniFileHelper = class helper for TCustomIniFile
public procedure UpdateFile; end; procedure TIniFileHelper.UpdateFile; begin ShowMessage('X'); inherited UpdateFile; end; procedure TForm1.Button2Click(Sender: TObject); var IniF: TCustomIniFile; begin IniF := TMemIniFile.Create('......'); try IniF.WriteString('section', 'ident', 'value'); finally IniF.UpdateFile; IniF.Free; end; end; |
AW: Ini-Writes global abfangen?
Leider kein Glück. Nur ein kleines MemoryLeak am Ende.
|
AW: Ini-Writes global abfangen?
Seit wann kann ein Class-Helper Methoden überschreiben und sich damit in die Vererbungs-Hierarchie einklinken? Soweit ich weiß, lassen sich lediglich neue Methoden hinzufügen.
Würde der Class-Helper also eine Methode "Update2" einführen, könnte aus dieser heraus die originale Methode "UpdateFile" ausgerufen werden. Das ist nicht der transparente Ablauf, der gewünscht ist, aber damit sollte am Ende eine INI-Datei geschrieben worden sein. Mein Ansatz wäre eine eigene INI-Klasse. |
AW: Ini-Writes global abfangen?
Danke für den Hinweis Daniel, hab nicht mit gedacht -.-
Vorher war auch nicht die Rede vom Custom sondern nur von TIniFile |
AW: Ini-Writes global abfangen?
Mhh jetzt bin ich hin und her gerissen.
Heißt das, dass ich am Ende doch gezwungen bin im ganzen Projekt Anpassungen vorzunehmen? Ich verwende überall TCustomIniFile und TMemIniFile. Das abzuändern in TMeineIniKlasse wäre ja möglich. Aber wie würdest du denn dann UpdateFile aufrufe? Ein eigenes UpdateFile was dann das richtige UpdateFile aufruft? |
AW: Ini-Writes global abfangen?
Wenn man schon die Klasse ableitet, spricht aus meiner sicht nichts dagegen auch das UpdateFile zu überschreiben. Die Grundfunktion bleibt ja die gleiche und wird erweitert.
|
AW: Ini-Writes global abfangen?
Naja, mehrere Wege führen nach Rom.
Eine weitere Option wäre ein sog. "Virtual Method Interceptor". Damit kannst Du eine Art Benachrichtigungs-Mechanismus implementieren, der auf Methoden anderer Klassen reagiert. ![]() Je nachdem, was Dein eigentliches Ziel ist, setzt Du dies auf .UpdateFile oder gleich auf die WriteXX-Methoden an. Diese Lösung ist dahingehend sehr mächtig, Du kannst die Werte der an die eigentliche Methode zu übergebenen Parameter abfragen und ändern oder den Aufruf gänzlich unterbinden. |
AW: Ini-Writes global abfangen?
Um euch aufzuklären:
wenn eine bestimmte Situation im Programm gegeben ist, sollte UpdateFile nichts schreiben dürfen. Das war mein Hintergedanke. Ich könnte auch vor jedes UpdateFile einfach ein Abfrage schreiben aber das wäre eine echte 0815-Lösung. |
AW: Ini-Writes global abfangen?
Warum nicht einfach eine Kopie der System.Inifiles Unit im das Projekt aufnehmen
und die gewünschten Änderungen dort einpflegen? Dann sind keine Änderungen ansonsten im Code notwendig |
AW: Ini-Writes global abfangen?
Daran habe ich auch schon gedacht. Aber darf ich das so einfach machen?
Leider habe ich "AutoSave" noch nicht (ab Berlin). Das würde ich mir dann in der eigenen Unit nachrüsten. |
AW: Ini-Writes global abfangen?
Zitat:
|
AW: Ini-Writes global abfangen?
Meine Lösung für alle die es interessiert:
Delphi-Quellcode:
unit classIni;
interface uses SysUtils, IniFiles; type TMemIniFile_ = class(TMemIniFile) private FAutoSave: Boolean; public constructor Create(const FileName: string; const AutoSave: Boolean = True); overload; constructor Create(const FileName: string; const Encoding: TEncoding; const AutoSave: Boolean = True); overload; destructor Destroy; override; end; implementation {TMemIniFile} constructor TMemIniFile_.Create(const FileName: string; const Encoding: TEncoding; const AutoSave: Boolean = True); begin inherited Create(FileName, Encoding); FAutoSave := AutoSave; end; constructor TMemIniFile_.Create(const FileName: string; const AutoSave: Boolean = True); begin inherited Create(FileName); FAutoSave := AutoSave; end; destructor TMemIniFile_.Destroy; begin if FAutoSave then UpdateFile; inherited; end; end.
Delphi-Quellcode:
Info zum Parameter False:
procedure TForm1.Button2Click(Sender: TObject);
var IniF: TCustomIniFile; begin IniF := TMemIniFile_.Create('.....', False); try IniF.WriteString('section', 'ident', 'value'); finally IniF.Free; end; da sollte natürlich nicht einfach nur False stehen, da es nicht wirklich Sinn ergibt. Mein Problem bestand darin, dass mein Programm keine Ini-Dateien schreiben soll, wenn es im aktuellen Verzeichnis keine Schreibrechte hat. Das prüft mein Programm beim start und setzt ggf. eine globale Variable (eigene Unit, Record) auf False. Diese Variable übergebe ich dann als zweiten "AutoSave"-Parameter. Wer mindestens Berlin hat, braucht das nicht. Gefunden hier: ![]() |
AW: Ini-Writes global abfangen?
Mal eine Konkretisierung von Daniels Vorschlag:
Du könntest eine Unit uMyIniFiles erstellen und TIniFiles "überschreiben".
Delphi-Quellcode:
Wenn Du jetzt in allen uses-Blöcken deines Projektes IniFiles durch uMyIniFiles ersetzt kannst Du nun Blocking verwenden und damit UpdateFiles beeinflussen.
TIniFile = class(IniFiles.TIniFile)
public Blocking: Boolean; procedure UpdateFile; override; end; ... procedure TIniFile.UpdateFile; begin if not Blocking then inherited; end; Du kannst so eine bestehende Klasse verändern (bei Beibehaltung des Klassennamens), wenn die betreffende Funktionalität überschreibbar ist. |
AW: Ini-Writes global abfangen?
Bei dieser Umsetzung habe ich jedoch eine Frage.
Wie sieht die konkrete Unit aus, wenn ich TMemIniFile benutzen möchte? Denn eine Deklaration wie myIniFile: TCustomIniFile; funktioniert mit der Unit ja dann nicht mehr. |
AW: Ini-Writes global abfangen?
PS: UpdateFile hilft nur bei der MemIni.
Die normale INI-API ändert die Datei immer sofort, mit jedem einzelnem WriteXyz-Befehl. |
AW: Ini-Writes global abfangen?
Zitat:
Aber ich glaube ich sollte überall TCustomIniFile durch TMemIniFile ersetzen. Weil ich benutze TIniFile ja eh nicht. |
AW: Ini-Writes global abfangen?
Meine finale Unit. Funktioniert im kompletten Projekt einwandfrei:
Delphi-Quellcode:
Aufruf
unit classIni;
interface uses SysUtils, IniFiles; type TMemIniFile = class(IniFiles.TMemIniFile) private FAutoSave: Boolean; public constructor Create(const FileName: string; const AutoSave: Boolean = True); overload; procedure UpdateFile; override; end; implementation {TMemIniFile} constructor TMemIniFile.Create(const FileName: string; const AutoSave: Boolean = True); begin inherited Create(FileName); FAutoSave := AutoSave; end; procedure TMemIniFile.UpdateFile; begin if FAutoSave then inherited; end; end.
Delphi-Quellcode:
Sinn zusammengefasst damit es jeder versteht:
procedure TForm1.Button2Click(Sender: TObject);
var IniF: TMemIniFile; begin IniF := TMemIniFile.Create(....., <irgendeine-boolsche-variable>); try IniF.WriteString('section', 'ident', 'value'); finally IniF.UpdateFile; // wird nur wirklich ausgeführt, wenn die irgendeine-boolsche-variable true ist IniF.Free; end; end; das hier ist dasselbe wie der AutoSave-Parameter ab Berlin. Mit diesem Code verhindere ich Fehler wenn mein Programm in einem UAC-geschütztem Verzeichnis gestartet wird. Teamspeak macht es ähnlich. Bei Programmstart kommt in einem UAC-geschützten Verzeichnis die Meldung Zitat:
|
AW: Ini-Writes global abfangen?
Wo schreibst du deine Daten denn hin ?
Wäre es nicht besser bei geschützem Verzeichnis in das User-Documents-Verzeichnis, oder direkt immer in das User-Documents-Verzeichnis mit evtl. weiteren Unterverzeichnisse zu schreiben ? Ich habe mittlerweile schreibende Zugriffe fast nur dort erlaubt, weil es woanders mal geht und mal nicht. Obwohl shared oder common kann man nicht immer darauf zugreifen, insbesondere auf verschiedenen Platformen. Vermutlich geht es dir um die gleiche Problematik. Der Vorteil wäre: es geht immer. Der Nachteil: es gibt Steuerdateien im User-Documents-Verzeichnis, die der mal löschen könnte. Das Thema Sicherheit spielt bei meinen Anwendungen keine Rolle, das könnte natürlich auch noch so ein Punkt sein. Rollo |
AW: Ini-Writes global abfangen?
Warum nicht einen ganz anderen Ansatz?
nur mal so schnell getippt als Anregung: Das ganze über Interfaces realisieren. Stichwort testbar etc.... 3 Units: Interface
Delphi-Quellcode:
Implementation
unit my.ini.interfaces;
interface type // Hier alles rein was im Prog tatsäclich benötigt wird iIniReadWriter = interface ['{AE9CC6E5-F0B8-4D39-8F6C-799423C60A37}'] function ReadString(const Section, Ident, Default: string): string; procedure WriteString(const Section, Ident, Value: String); end; implementation end.
Delphi-Quellcode:
Factory
unit my.ini.implementations;
interface uses System.SysUtils, System.IniFiles, my.ini.interfaces; type tiniReadWriterFile = class(TInterfacedObject, iIniReadWriter) private fchanged : boolean; fautosave: boolean; fini : TMemIniFile; private // iIniReadWriter implementations function ReadString(const Section, Ident, Default: string): string; procedure WriteString(const Section, Ident, Value: String); public constructor Create(const FileName: string; const autosave: boolean = true); overload; constructor Create(const FileName: string; const Encoding: TEncoding; const autosave: boolean = true); overload; destructor Destroy; override; end; implementation constructor tiniReadWriterFile.Create(const FileName: string; const Encoding: TEncoding; const autosave: boolean = true); begin inherited Create; fini := TMemIniFile.Create(FileName, Encoding); fautosave := autosave; end; constructor tiniReadWriterFile.Create(const FileName: string; const autosave: boolean = true); begin inherited Create; fini := TMemIniFile.Create(FileName); fautosave := autosave; end; destructor tiniReadWriterFile.Destroy; begin if fautosave and fchanged then fini.UpdateFile; fini.free; inherited; end; function tiniReadWriterFile.ReadString(const Section, Ident, Default: string): string; begin result := fini.ReadString(Section, Ident, Default); end; procedure tiniReadWriterFile.WriteString(const Section, Ident, Value: String); begin fini.WriteString(Section, Ident, Value); fchanged := true; end; end.
Delphi-Quellcode:
unit my.ini.factory;
interface uses my.ini.interfaces; type tIniReadWriterTypes = (tirwFileWrite, tirReadonly, tirwLogging) ; function CreateIniReadWriterFactory(aType : tIniReadWriterTypes; const Filename : String): iIniReadWriter; implementation uses my.ini.implementations; function CreateIniReadWriterFactory(aType : tIniReadWriterTypes; const Filename : String): iIniReadWriter; begin case aType of tirwFileWrite: result := tiniReadWriterFile.Create(Filename, true); tirReadonly :Result := tiniReadWriterFile.Create(Filename, false); // tirwLogging: result := tiniReadWriterLogger.Create(Filename); end; end; end.
Delphi-Quellcode:
unit my.useini;
interface uses my.ini.interfaces; type tuseini = class private public procedure SaveSettings(Writer : iIniReadWriter); end; implementation { tuseini } procedure tuseini.SaveSettings(Writer: iIniReadWriter); begin assert(Writer <> nil, 'Writer must be set'); Writer.WriteString('Test', 'Dummy', 'Default'); end; end. TestProjekt
Delphi-Quellcode:
program Project13;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, my.ini.implementations in 'my.ini.implementations.pas', my.ini.interfaces in 'my.ini.interfaces.pas', my.ini.factory in 'my.ini.factory.pas', my.useini in 'my.useini.pas'; const aFile : String = 'c:\test\Test.ini'; procedure Test; Var UseIni : tuseini; begin Useini := tuseini.create; Writeln('Test Readonly') ; Useini.SaveSettings(CreateIniReadWriterFactory(tirReadonly, aFile)); Writeln('Readonly Done'); Useini.SaveSettings(CreateIniReadWriterFactory(tirwFileWrite, aFile)); Writeln('Write Ini Done') ; Useini.free; end; begin try Test; Writeln('Sucess'); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; readln; end. |
AW: Ini-Writes global abfangen?
Zitat:
Aber ich gucke mir das trotzdem mal an! Zitat:
Er, der Benutzer, hat dabei die Möglichkeit zu wählen, ob die Anwendung portabel einsetzbar sein soll. Heißt also, es kann im programm-eigenen-Verzeichnis und in AppData\Programmname gespeichert werden. Die von mir bezogene Änderung bezieht sich nur auf die Variante, wenn Einstellungen im programm-eigenen-Verzeichnis gespeichert werden sollen, es aber wegen fehlender Rechte nicht funktioniert. |
AW: Ini-Writes global abfangen?
Zitat:
Wann wird denn <irgendeine-boolsche-variable> mit einem sinnvollen Wert gefüllt? Danke und Gruß Edit: Der Einwand mit dem const-Parameter war Unfug, ich nehme ihn zurück. |
AW: Ini-Writes global abfangen?
Das ist ein Parameter, der angibt, ob gespeichert werden soll und nicht, ob gespeichert werden kann.
|
AW: Ini-Writes global abfangen?
Zitat:
Gibt für mich zwar spontan keinen Sinn, denn Ini-Dateien sollen doch (eigentlich) immer gespeichert werden, sonst wären es doch keine (echten) Dateien, ohne Datenträger maximal noch Memory-Dateien, aber sei's drum, ich werde mich weiter damit beschäftigen. |
AW: Ini-Writes global abfangen?
soll: es soll automatisch (bpätestens beim Free/Close gespeichert werden
kann: es sagt nicht aus, dass nicht dennoch später noch gespeichert werden könnte (Update) Und was einen Sinn betrifft. Wenn nichts gespeichert werden soll, warum werden dann überhaupt die Write-Funktionen aufgerufen, wenn doch eh nicht gespeichert werden soll? Eingaben "wortlos" einfach so zu verwerfen ist eines der größten Fehlerpotentiale, die man in eine Komponente einbauen kann. siehe TStrings.Objects ... ständigt wundert sich jemand, dass die Werte verschwinden, obwohl etwas zugewiesen wurde. Eine Exception ala "Speichern/Objects nicht implementiert" hätte da sofort den Fehler aufgezeigt. für mich ist soein Verhalten fast genauso schlimm wie ein
Delphi-Quellcode:
im Code
try ... except end;
und später heult dann jemand rum "mein Programm macht nie das, was ich will und ich weiß nicht warum :cry:". |
AW: Ini-Writes global abfangen?
Er hat das doch schon ausführlich erklärt, dass es Konstellationen gibt, bei dem die INI-Datei schreibgeschützt ist und dann sollen die Werte halt nicht (zurück)geschrieben werden und anstatt jetzt an 23.345 Stellen im Code zu sagen
Delphi-Quellcode:
wird das schreiben so an einer zentralen Stelle verhindert. Im Hinterkopf behalten: Es geht um TMemIni, d.h. das schreiben an sich funktioniert ja (in die In-Memory Ini), diese wird am Ende ggf. nur nicht wieder als Datei zurück geschrieben.
If IniBeschreibbar then Ini.WriteString(...)
|
AW: Ini-Writes global abfangen?
Dann sollte die INI das aber auch sagen, wenn sie es nicht will.
Delphi-Quellcode:
uses
SysUtils, IniFiles; type TAccessIniFile = class(TMemIniFile) private FReadOnly: Boolean; procedure SetReadOnly(Value: Boolean); public constructor Create(const FileName: string; ReadOnly: Boolean=False); overload; procedure WriteString(const Section, Ident, Value: String); override; procedure DeleteKey(const Section, Ident: String); override; procedure EraseSection(const Section: string); override; procedure UpdateFile; override; property ReadOnly: Boolean read FReadOnly write SetReadOnly; end; { TAccessIniFile } constructor TAccessIniFile.Create(const FileName: string; ReadOnly: Boolean); begin inherited Create(FileName); FReadOnly := ReadOnly; end; procedure TAccessIniFile.DeleteKey(const Section, Ident: String); begin if FReadOnly then raise Exception.Create('ReadOnly'); inherited; end; procedure TAccessIniFile.EraseSection(const Section: string); begin if FReadOnly then raise Exception.Create('ReadOnly'); inherited; end; procedure TAccessIniFile.SetReadOnly(Value: Boolean); begin if not FReadOnly and (FReadOnly <> ReadOnly) then UpdateFile; FReadOnly := ReadOnly; end; procedure TAccessIniFile.UpdateFile; begin //if csDestroying in ComponentState then // Exit; //if FReadOnly then // raise Exception.Create('ReadOnly'); //inherited; if not FReadOnly then inherited; end; procedure TAccessIniFile.WriteString(const Section, Ident, Value: String); begin if FReadOnly then raise Exception.Create('ReadOnly'); inherited; end; |
AW: Ini-Writes global abfangen?
Zitat:
Zitat:
Hier meine aktuelle Unit. Den Parameter "AutoSave" gibt es nicht mehr. Stattdessen werden Write-Befehle nicht ausgeführt, wenn ReadOnly True ist.
Delphi-Quellcode:
unit classIni;
interface uses SysUtils, IniFiles; type TMemIniFile = class(IniFiles.TMemIniFile) private FModified, FReadOnlyMode: Boolean; function IsNumeric(const aString: string; const bAcceptNegativeNumbers: Boolean = True): Boolean; procedure FlushModifications; public constructor Create(const FileName: string; const ReadOnlyMode: Boolean = False); overload; constructor Create(const FileName: string; const Encoding: TEncoding; const ReadOnlyMode: Boolean = False); overload; destructor Destroy; {* reintroduce; *} override; procedure UpdateFile; override; procedure WriteString(const Section, Ident, Value: string); reintroduce; overload; procedure WriteInteger(const Section, Ident: string; Value: Integer); reintroduce; overload; procedure WriteBool(const Section, Ident: string; Value: Boolean); reintroduce; overload; function ReadInt64(const Section, Ident: string; Default: Int64): Int64; procedure WriteInt64(const Section: string; const Ident: string; Value: Int64); end; implementation // Helper // ============================================================================================================================================== function TMemIniFile.IsNumeric(const aString: string; const bAcceptNegativeNumbers: Boolean = True): Boolean; var bRes: Boolean; begin bRes := StrToInt64Def(aString, 0) = StrToInt64Def(aString, 1); if bRes and (not bAcceptNegativeNumbers) and (StrToInt64(aString) < 0) then bRes := False; Result := bRes; end; procedure TMemIniFile.FlushModifications; begin // Flush the modifications to disk if (not FReadOnlyMode) and FModified then begin inherited UpdateFile; FModified := False; end; end; // ============================================================================================================================================== // Create / Update // ============================================================================================================================================== constructor TMemIniFile.Create(const FileName: string; const ReadOnlyMode: Boolean = False); begin inherited Create(FileName); FReadOnlyMode := ReadOnlyMode; FModified := False; end; constructor TMemIniFile.Create(const FileName: string; const Encoding: TEncoding; const ReadOnlyMode: Boolean = False); begin inherited Create(FileName, Encoding); FReadOnlyMode := ReadOnlyMode; FModified := False; end; destructor TMemIniFile.Destroy; begin // If not already flushed to disk, flush modifications now FlushModifications; inherited Destroy; end; procedure TMemIniFile.UpdateFile; begin // Flush the modifications to disk FlushModifications; end; // ============================================================================================================================================== // Writes / Reads // ============================================================================================================================================== procedure TMemIniFile.WriteString(const Section, Ident, Value: string); begin if not FReadOnlyMode then begin inherited WriteString(Section, Ident, Value); FModified := True; end; end; procedure TMemIniFile.WriteInteger(const Section, Ident: string; Value: Integer); begin WriteString(Section, Ident, IntToStr(Value)); end; procedure TMemIniFile.WriteBool(const Section, Ident: string; Value: Boolean); const Values: array [Boolean] of string = ('0', '1'); begin WriteString(Section, Ident, Values[Value]); end; function TMemIniFile.ReadInt64(const Section: string; const Ident: string; Default: Int64): Int64; var sTmp: string; begin sTmp := ReadString(Section, Ident, SysUtils.IntToStr(Default)); if IsNumeric(sTmp, True) then Result := StrToInt64(sTmp) else Result := Default; end; procedure TMemIniFile.WriteInt64(const Section: string; const Ident: string; Value: Int64); begin WriteString(Section, Ident, SysUtils.IntToStr(Value)); end; // ============================================================================================================================================== end. |
AW: Ini-Writes global abfangen?
Zitat:
|
AW: Ini-Writes global abfangen?
War noch ein Überbleibsel von gestern, muss natürlich weg. Gestern hatte ich das Problem, dass der Destruktor gar nicht erst aufgerufen wurde. Ist jetzt aber behoben.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:00 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz