Dopisywanie wartości typu AnsiString do wszelkiego rodzaju plików (*.exe, *.bmp, *.dll, itp...).

    W tej poradzie pokażę jak można dopisywać tekst (wartości typu AnsiString) do wszelkiego rodzaju plików, takich jak np. aplikacje *.exe, pliki graficzne wszelkiego rodzaju i wszystkie pozostałe pliki. Przedstawiona metoda, ma to do siebie, że nie wpływa w żaden sposób na funkcjonowanie pliku. Jeżeli dopiszecie coś np. do pliku *.exe na pomocą Notatnika lub nawet edytora Hexadecymalnego, to taki plik przestanie funkcjonować, nie uruchomi się. Metoda którą tutaj przedstawiam rozwiązuje ten problem. Tekst zostanie dopisany do pliku w zdarzeniu OnClick przycisku Button1, natomiast w zdarzeniu OnClick przycisku Button2 to co zostało dopisane, będzie odczytane z pliku i przepisane do obiektu Memo1, oczywiście zawartość można przepisać do dowolnego obiektu który obsługuje wyświetlanie wartości typu AnsiString, czyli np. TEdit, TLabel, TRichEdit, itp...

// Plik źródłowy np. Unit1.cpp
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include <fstream>
#include <stdio.h>
                /
/Te wpisy należy dołączyć do sekcji include
#include <
io.h>  
using namespace std;
//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//--------------------------------
void savetoexe(String fname, String str)
{
 FILE* f;
 if(!(f = fopen(fname.c_str(), "r+")))
  return;

 long seekOff = 0;
 int fd = fileno(f), count = 0;
 String txtSize;
 int x = str.Length();
 txtSize = Format("%d", ARRAYOFCONST((x)));
 char digit;

 do
 {
  fseek(f, --seekOff, SEEK_END);
  digit = getc(f);
 }
 while
  (digit >= '0' && digit <= '9');

 long fSize = filelength(fd);
 ++seekOff;

 if(!seekOff || -seekOff >= fSize)
 {
  fseek(f, 0, SEEK_END);
  fwrite(str.c_str(), sizeof(char), str.Length(), f);
  fwrite(txtSize.c_str(), sizeof(char), txtSize.Length(), f);
 }
 else
 {
  char *txt;
  txt = new char[-seekOff + 1];
  fread(txt, sizeof(char), -seekOff, f);
  txt[-seekOff] = '\0';
  sscanf(txt, "%d", &count);
  fseek(f, seekOff - count, SEEK_END);
  fwrite(str.c_str(), sizeof(char), str.Length(), f);
  fwrite(txtSize.c_str(), sizeof(char), txtSize.Length(), f);
  fSize -= count - seekOff;
  delete txt;
 }

fflush(f);
chsize(fd, fSize + str.Length() + txtSize.Length());
fclose(f);
}
//--------------------------------
String loadfromoexe(String fname)
{
 FILE* f;
 if(!(f = fopen(fname.c_str(), "r")))
  return String("");

 long seekOff = 0;
 int fd = fileno(f), count = 0;
 char digit;

 do
 {
  fseek(f, --seekOff, SEEK_END);
  digit = getc(f);
 }
 while
  (digit >= '0' && digit <= '9');

 long fSize = filelength(fd);
 ++seekOff;
 if(!seekOff || -seekOff >= fSize)
 return String("");

 char *txt, *res;
 txt = new char[-seekOff + 1];
 fread(txt, sizeof(char), -seekOff, f);
 txt[-seekOff] = '\0';
 sscanf(txt, "%d", &count);

 if(!count)
 {
  delete txt;
  return String("");
 }

 res = new char[count + 1];
 fseek(f, seekOff - count, SEEK_END);
 fread(res, sizeof(char), count, f);
 res[count] = '\0';
 delete txt;
 fclose(f);
 String resTxt(res, count);
 delete res;
 return resTxt;
}
//--------------------------------
String split(String str, int s, String sep)
{
 String res;
 TStringList *Lista = new TStringList;
 int x = str.Length();

 for(int i = 0; i < str.Length(); i++)
 {
  if(x > 0)
  {
   x = str.LastDelimiter(sep);
   Lista->Add(str.SubString(x + 1, str.Length() - x));
   str = str.Delete(x, str.Length() - x + 1);
  }
 }

 res = Lista->Strings[Lista->Count - s];
 delete Lista;
 return res;
}

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 String fname = "program.exe", str = "pierwszy|drugi|trzeci|czwarty";
 savetoexe(fname, str);
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 String fname = "program.exe";
 Memo1->Lines->Add(loadfromoexe(fname));
}
//--------------------------------

Metoda którą tutaj przedstawiłem odczytuje zapisany ciąg znaków, czyli: "pierwszy|drugi|trzeci|czwarty" i przepisuje go do Memo1, w tym ciągu znaków można dopatrzyć się pionowych kresek, wstawiłem je tam ponieważ będą pomocne w dalszej części, otóż oprócz tego, że zapisaliśmy ten ciąg, możemy go jeszcze podzielić na części, stosując separator, w tym przypadku jako separatora użyłem właśnie pionowej kreski "|", do podzielenia ciągu znaków na części posłuży - podana już w przykładzie - funkcja split, a tak będzie to wyglądało w praktyce:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 String x = loadfromoexe("program.exe"), sep = "|"; // zmienna sep przechowuje rodzaj użytego separatora.
 try
 {
  for(int i = 1; i <= 4; i++)
  Memo1->Lines->Add(split(x, i, sep));
 }
 catch(...)
 {
  Application->MessageBox("Nie można pobrać danych. Prawdopodobnie nie zostały zapisane"
  " w podanej lokalizacji.", "Błąd", MB_ICONSTOP);
 }
}
//---------------------------------

Ta metoda oczywiście również działa, możne w ten sposób dzielić nie tylko pojedyncze wyrazy, ale całe zdania, przy czym kod się nie zmienia. Całość można jednak jeszcze poprawić, ponieważ przy odczytywaniu i dzieleniu wyrazów, w powyższym przykładzie podałem jawnie ilość dzielonych części z których składa się łańcuch znaków, czyli była to liczba 4. Lepszym rozwiązaniem jednak będzie takie kod, który sam obliczy z ilu części składa się łańcuch znaków:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 String x = loadfromoexe("program.exe"), sep = "|"; // zmienna sep przechowuje rodzaj użytego separatora.
 String y = x;
 int z = y.Length();
 try
 {
  for(int i = 1; i <= y.Length(); i++)
  {
   if(z > 0)
   {
    z = y.LastDelimiter(sep);
    y = y.Delete(z, y.Length() - z + 1);

    Memo1->Lines->Add(split(x, i, sep));
   }
}
 }
 catch(...)
 {
  Application->MessageBox("Nie można pobrać danych. Prawdopodobnie nie zostały zapisane"
  " w podanej lokalizacji.", "Błąd", MB_ICONSTOP);
 }
}
//---------------------------------

Na zakończenie kilka istotnych uwag, przede wszystkim nie można zapisać w ten sposób danych do aktywnego procesu w systemie, a to z kolei oznacza, że program nie zapisze nic "sam do siebie" czyli do własnego pliku, ponieważ  nie można powtórnie otworzyć pliku, który już jest aktywny, nie dotyczy to np. plików graficznych, które nie posiadają własnych procesów, ponieważ są wczytywane do programów. W ten sposób nie można zapisać danych do żadnego aktualnie otwartego programu, jednak program może "sam z siebie" czytać.
    Jeśli chcielibyśmy przywrócić oryginalną wersję pliku to robi się problem, ponieważ nawet jeżeli podamy jako parametr zmiennej str wartość "" (nic), to plik i tak będzie dłuższy o jeden bajt, dlatego że w pliku oprócz samego łańcucha znaków zapisywana jest również długość tego łańcucha, więc jeśli zapiszemy do pliku "nic" to i tak na końcu pliku pojawi się wartość 0, jeżeli usuniemy z kodu zapisywanie długości łańcucha znaków, to po pierwsze  nie będzie można go odczytać, a po wtóre na końcu pliku i tak pojawi się znak końca o długości jednego bajta, tak więc nie można w ten sposób przywrócić oryginalnego rozmiaru pliku, zawsze będzie większy o jeden bajt.