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