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; } //--------------------------------
//-------------------------------- |
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.