Entwickler-Ecke
Dateizugriff - ...Daten in der eigenen Exe speichern ohne weiteres Programm
jaenicke - Mo 26.01.09 09:10
1. Vorüberlegungen
Ich muss eindeutig herausfinden können, ob Daten angehängt wurden. Dafür hänge ich an die eigentlichen Daten einen Integerwert und einen Prüfwert dafür an, sowie eine Signatur. Sind diese Angaben konsistent, dann sind angehängte Daten vorhanden.
Um meine alte Exe zu ersetzen benenne ich zunächst meine Exe um. Dies geht auch während diese läuft. Jetzt kann ich eine neue Datei mit dem alten Originalnamen meiner Exe erstellen. In diese Datei kopiere ich die Exe und die angehängten Daten sowie die Signatur.
Jetzt habe ich aber immer noch die umbenannte Originalexe auf der Festplatte. Deshalb starte ich jetzt die neue Exe mit einem entsprechenden Parameter und beende die alte. Sobald dies möglich ist, löscht die neue Exe die alte. Und damit liegt nur noch die eine (neue) Exe auf der Festplatte.
Jetzt interessieren mich diese Interna der Umsetzung ja wenig, wenn ich einfach nur Daten an meine Exe anhängen will. Und deshalb habe ich eine Klasse geschrieben, die diese Arbeit abnimmt und komplett vor dem Benutzer versteckt. Diese stelle ich im nächsten Teil direkt hier drunter vor.
Noch ein Zusatzhinweis:
Eventuell müssen beim Neustart der neu geschriebenen Exe Daten an die neu gestartete Exe übergeben werden. Zum Beispiel offene Dateien, die wieder geöffnet werden sollen.
Dazu wurde z.B. hier in der Library eine Möglichkeit vorgestellt, so dass ich dies nicht selbst noch einmal schreiben brauche:
http://www.delphi-library.de/viewtopic.php?p=121427
Ich überlege, diese Möglichkeit zu integrieren. Sollte ich das verwirklichen, dann wird dies in dem Thread für die Vorstellung der Unit zu finden sein:
http://www.delphi-forum.de/viewtopic.php?p=545927
jaenicke - Mo 26.01.09 09:11
2. Umsetzung als Nachfahre von THandleStream
Vorweg: der komplette (und jeweils aktuellste) Code ist in dem eben bereits verlinkten Thread mit der Vorstellung der Unit zu finden:
http://www.delphi-forum.de/viewtopic.php?p=545927
THandleStream ist eine von TStream abgeleitete Klasse, die Lese- und Schreibfunktionen etc. stehen also schon zur Verfügung. Jetzt ist die Frage: Was ist eigentlich anders, wenn ich nur einen bestimmten Bereich der Datei als schreibbar definiere?
Ich muss Größenabfragen, Positionsänderungen und -abfragen, Lese- und Schreibzugriffe entsprechend verändern, so dass die Position 0 zwar mitten in der Datei liegt, aber dennoch Position 0 bezogen auf den Stream ist. Helfen tut dabei, dass dies alles mit Seek realisiert wird. Beispiel dafür:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| function TStream.GetSize: Int64; var Pos: Int64; begin Pos := Seek(0, soCurrent); Result := Seek(0, soEnd); Seek(Pos, soBeginning); end; |
Das heißt ich muss nur Seek entsprechend umschreiben.
Beim Erstellen des Streams zum Schreibzugriff kopiere ich die Exe in eine temporäre Datei und entferne dabei eine evtl. bereits vorhandene Signatur. D.h. bspw. ein Lesezugriff am Ende der Datei erzeugt ganz normal einen Fehler.
Wenn ich aber die Exe nur zum Lesen öffne, dann greife ich direkt auf die Exe zu. Deshalb muss ich beim Lesen verhindern, dass mehr gelesen wird als an Daten verfügbar ist. Ansonsten könnte auch die Signatur mit gelesen werden.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| function TExeDataStream.Read(var Buffer; Count: Longint): Longint; begin if FReadOnly and (Position + Count > Size) then Count := Size - Position; Result := inherited Read(Buffer, Count); end; |
Ich setze also die Anzahl der zu lesenden Zeichen auf die Anzahl der nach der Position noch verfügbaren, wenn der Lesebereich zu groß ist.
Ich denke das meiste wird auch durch die Kommentare im Quelltext deutlich. Auf Seek möchte ich hier noch genauer eingehen.
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| function TExeDataStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; var NewOffset: Int64; begin NewOffset := Offset; case Origin of |
Es gibt drei verschiedene Bezugspositionen. Anfang der Datei, aktuelle Position und Ende der Datei. Ich habe hinter den Zeilen symbolisiert an welcher Stelle der neue Offset liegt. Der Punkt zeigt an, ob der neue Offset im Bereich der ursprünglichen Exe, der angehängten Daten oder der Signatur liegt. NewOffset hat ja den übergebenen Wert Offset und wird dann ggf. angepasst.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| soBeginning: if NewOffset <= 0 then NewOffset := FExeSize else if FReadOnly and (NewOffset > Seek(0, soEnd)) then NewOffset := Seek(0, soEnd) else NewOffset := FExeSize + NewOffset; |
Bei einem Offset bezogen auf den Start darf dieser nicht negativ sein. Wenn die Exe nur zum Lesen direkt geöffnet ist, dann darf der Offset auch nicht hinter dem Ende (in der Signatur oder dahinter) liegen. Ist der Offset gültig, dann wird er auf einen Wert in der gesamten Datei umgerechnet.
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| soCurrent: if (NewOffset < 0) and (Position + NewOffset < 0) then NewOffset := -Position else if FReadOnly and (NewOffset > 0) and (Position + NewOffset > Size) then NewOffset := Size - Position; |
Wenn der Offset vor dem Start des Datenbereichs läge, dann wird er auf -Position gesetzt, was dann der Anfang des Datenbereichs wäre. Genauso wird er beim schreibgeschützten Lesen der Exe ggf. auf das Ende des Datenbereichs gestutzt.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| soEnd: if NewOffset >= 0 then if FReadOnly then NewOffset := -GetSignatureSize else NewOffset := 0 else if -NewOffset > Seek(0, soEnd) then NewOffset := -Seek(0, soEnd) else if FReadOnly then NewOffset := NewOffset - GetSignatureSize end; |
Bei einem Bezug auf das Dateiende muss die Signatur ggf. eingerechnet werden, wenn diese bei einem schreibgeschützten Zugriff noch vorhanden ist.
Delphi-Quelltext
1: 2:
| Result := inherited Seek(NewOffset, Origin) - FExeSize; end; |
Am Ende wird dann die ursprüngliche Version von Seek für die eigentliche Durchführung des Vorgangs aufgerufen. Da sich die zurückgegebene Position auf die gesamte Datei bezieht, muss diese auf den Datenbereich umgerechnet werden.
Dies sind die wichtigsten Stellen des Quelltextes, der Rest sollte aus den Kommentaren klar werden.
jaenicke - Mo 26.01.09 09:11
3. Beispielprogramm
Die Demo besteht nur aus einem Memo, einem Button zum Hinzufügen des Memoinhalts und einem zum Entfernen der angehängten Daten. Außerdem zeigt ein Label den Status an. Die Demo (inkl. der Unit wie sie zum Zeitpunkt dieses Beitrages aktuell war) befindet sich im Anhang. Zur Benutzung der Unit empfehle ich die jeweils neueste Variante im Vorstellungsthread als Open Source Unit:
http://www.delphi-forum.de/viewtopic.php?p=545927
Die Demo ist sehr kurz. Anhängen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| procedure TForm136.Button1Click(Sender: TObject); var FileContents: TExeDataStream; begin FileContents := TExeDataStream.Create(); try FileContents.WriteString(Memo1.Text); finally FileContents.Free; end; end; |
Anhang löschen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| procedure TForm136.Button2Click(Sender: TObject); var FileContents: TExeDataStream; begin FileContents := TExeDataStream.Create(); try FileContents.Size := 0; finally FileContents.Free; end; end; |
Beim Starten Inhalt auslesen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
| procedure TForm136.FormCreate(Sender: TObject); var FileContents: TExeDataStream; begin FileContents := TExeDataStream.Create(true); try if FileContents.Size > 0 then begin Label1.Caption := 'Inhalt gefunden!'; Label1.Font.Color := clRed; Memo1.Text := FileContents.ReadString; end; finally FileContents.Free; end; end; |
Ich denke einmal dazu gibt es nicht viel zu sagen, wenn die Funktionsweise von TStream klar ist.
Beim Freigeben des Objektes mit Free wird automatisch die eigentliche Dateioperation gestartet. Eine bessere Möglichkeit mit mehr Kontrolle darüber überlege ich noch zu implementieren, dazu findet ihr wie gesagt ggf. dann etwas im Vorstellungsthread der Unit.
http://www.delphi-forum.de/viewtopic.php?p=545927
Als Anleitung für das Anhängen von Daten genügt die aktuelle Version, deshalb werde ich neue Funktionen nur dort und nicht auch hier posten.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!