Autor Beitrag
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 20.01.09 23:36 
Daten in der eigenen Exe speichern ohne weiteres Programm [meta]Daten[/meta][meta]anhängen[/meta][meta]THandleStream[/meta][meta]TStream[/meta]

Da diese (gerade wieder aufgetauchte) Frage ja nicht das erste Mal gestellt wurde, möchte ich hier eine Möglichkeit zeigen, wie man Daten an die Exe anhängen und auslesen kann.

Ich habe im Internet ein Beispiel gesehen, bei dem die Größe der Exe als Konstante im Programm angegeben werden musste und ein zweites Programm benutzt wurde. Das ist aber gar nicht notwendig. Man muss sich nur eine Möglichkeit ausdenken, wie man sicher feststellen kann, ob Daten angehängt wurden oder nicht. Genau das beschreibe ich in diesem Beitrag.

Übersicht
  1. Vorüberlegungen
  2. Umsetzung als Nachfahre von THandleStream
  3. [url=www.delphi-library.d...ispielprogramm[/url]

Um es einfacher zu machen, habe ich eine Klasse geschrieben, die die Arbeit der Erkennung der Anhänge usw. abnimmt. Mehr dazu steht unter Punkt 2.

Die Unit mit der Klasse TExeDataStream habe ich in der entsprechenden Sparte vorgestellt:
www.delphi-forum.de/viewtopic.php?p=545927
Dort werde ich auch Weiterentwicklungen unabhängig von diesem FAQ-Eintrag posten. Es lohnt also ein Blick. ;-)


Zuletzt bearbeitet von jaenicke am Do 29.01.09 04:18, insgesamt 7-mal bearbeitet
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 26.01.09 10: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:
www.delphi-library.d...ewtopic.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:
www.delphi-forum.de/viewtopic.php?p=545927


Zuletzt bearbeitet von jaenicke am Mo 26.01.09 10:25, insgesamt 1-mal bearbeitet
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 26.01.09 10: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:
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:
ausblenden 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.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
function TExeDataStream.Read(var Buffer; Count: Longint): Longint;
begin
  // if we would read after the end, only read as much as there is left
  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.
ausblenden 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.
ausblenden 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.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
    soCurrent:
      if (NewOffset < 0and (Position + NewOffset < 0then
        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.
ausblenden 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.
ausblenden 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.


Zuletzt bearbeitet von jaenicke am Do 29.01.09 03:07, insgesamt 1-mal bearbeitet
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 26.01.09 10: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:
www.delphi-forum.de/viewtopic.php?p=545927

Die Demo ist sehr kurz. Anhängen:
ausblenden 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:
ausblenden 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:
ausblenden 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.
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.


FileAppendDemo - 545934.zip  (5.81 KB) Download (Rev 0)
 (1024x, 1024x gesamt)
Beschreibung:  


Zuletzt bearbeitet von jaenicke am So 15.02.09 20:08, insgesamt 1-mal bearbeitet

Für diesen Beitrag haben gedankt: Kawa