Die File Management Funktionen der WinAPI (1)
Allgemeines
In diesem Tutorial wird eine Klasse namens „File“ erstellt, mit der es möglich ist Daten in Dateien zu schreiben und sie wieder auszulesen. Die ganze Klasse
verwendet die File Management Funktionen der WinAPI, es muss also lediglich die windows.h Datei eingebunden werden. Die vollständige Liste der Funktionen
welche die WinAPI zum Umgang mit Dateien bereitstellt findet man
hier.
Die Klasse
Zuerst wird das Grundgerüst der Klasse deklariert. Sie enthält das Handle der Datei als private Membervariable sowie verschiedene Methoden. Diese werden
später implementiert. Um die Kompatibilität zu sowohl ANSI als auch UNICODE zu gewährleisten wird das TCHAR- und TEXT-Makro verwendet. Wird UNICODE
definiert, nimmt TCHAR den Typ "wchar_t" an, andernfalls "char". TEXT("String") würde bei UNICODE in L"String" umgewandelt werden, andernfalls in "String".
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class File
{
private:
//Dateihandle
void* FileHandle;
public:
//Konstruktor
File(void);
//Destruktor
~File(void);
//Methoden
bool IsOpen(void) const;
bool Close(void);
bool Open(const TCHAR* Filename, unsigned long Creation);
bool Write(const void* Data, unsigned int Size, unsigned long* WrittenBytes = NULL);
bool Read(void* Data, unsigned int Size, unsigned long* ReadBytes = NULL);
unsigned long GetSize(void) const;
bool SetPointerPosition(long Distance, unsigned long StartingPoint = FILE_CURRENT);
long GetPointerPosition(void) const;
};
|
Der Konstruktor
Im Kontruktor wird lediglich das Handle der Datei auf NULL gesetzt:
|
C-/C++-Quelltext
|
1
2
3
4
|
File::File(void)
{
FileHandle = NULL;
}
|
Der Destruktor
Der Destruktor prüft ob die Datei noch geöffnet ist. Ist dies der Fall wird sie geschlossen. Beide hier verwendeten Methoden werden später implementiert.
|
C-/C++-Quelltext
|
1
2
3
4
5
|
File::~File(void)
{
if (IsOpen() == true)
Close();
}
|
IsOpen
Diese Methode ist unerlässlich um Fehler abzufangen. Man weiß nie was passiert wenn man versucht mit einer noch nicht geöffneten Datei herumzuhantieren.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
|
bool File::IsOpen(void) const
{
if (FileHandle != INVALID_HANDLE_VALUE && FileHandle != NULL)
return true;
else
return false;
}
|
Wie man sieht prüft die Methode ob "FileHandle" gültig ist und ob es NULL ist. Ist es weder ungültig noch NULL gibt die Methode "true" zurück und
signalisiert damit dass die Datei geöffnet ist. Andernfalls wird "false" zurückgegeben.
Close
Wider Erwarten wird jetzt nicht erklärt wie eine Datei geöffnet wird, sondern erst wie man diese schließt. Nicht dass jemand auf die Idee kommt die
Funktion zum Öffnen schonmal zu testen ohne die Datei dann wieder zu schließen
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
|
bool File::Close(void)
{
if (IsOpen() == false)
return false;
if (CloseHandle(FileHandle) == FALSE)
return false;
FileHandle = NULL;
return true;
}
|
Zuerst wird wieder überprüft ob die Datei überhaupt geöffnet ist. Wenn nicht gibt die Methode "false" zurück. Ist sie geöffnet wird das Handle mithilfe
der Funktion "CloseHandle" geschlossen. Am Ende wird das Handle noch auf NULL gesetzt.
Open
Jetzt ist es endlich soweit: Die Methode "Open" öffnet eine bereits vorhandene Datei oder erstellt eine neue.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
|
bool File::Open(const TCHAR* Filename, unsigned long Creation)
{
//Wenn die Datei bereits geöffnet ist, wird sie nun geschlossen
if (IsOpen() == true)
Close();
//Öffnen der Datei, "CreateFile" wird bei UNICODE zu "CreateFileW", andernfalls zu "CreateFileA"
FileHandle = CreateFile(Filename, FILE_ALL_ACCESS, 0, NULL, Creation, FILE_ATTRIBUTE_NORMAL, NULL);
//Ist die Datei nun geöffnet?
return IsOpen();
}
|
Der erste Paramter ist der Name der Datei, der zweite beschreibt auf welche Weise die Funktion arbeiten soll. Zum Beispiel kann man
festlegen dass wenn eine Datei geöffnet werden soll aber noch nicht existiert diese neu erstellt wird. Die WinAPI definiert dafür
folgende Möglichkeiten:
CREATE_NEW
Erstellt eine neue Datei. Wennn bereits eine gleichnamige Datei existiert führt dies zu einem Fehler.
CREATE_ALWAYS
Erstellt immer eine neue Datei. Wenn bereits eine gleichnamige Datei existiert wird diese überschrieben.
OPEN_EXISTING
Öffnet eine bereits vorhandene Datei. Existiert diese nicht führt dies zu einem Fehler.
OPEN_ALWAYS
Öffnet immer eine Datei. Falls diese nicht existiert wird eine neue erstellt.
Zuerst wird nun überprüft ob die Datei bereits geöffnet wird. Ist sie das, wird sie geschlossen. So werden Fehler verhindert die auftreten könnten
falls versucht wird eine bereits geöffnete Datei nochmal zu öffnen.
Nun wird das Handle mit der Funktion "CreateFileW" gefüllt. Wirklich relevant sind hier nur 2 Parameter, wer etwas über die restlichen erfahren möchte
kann in der MSDN nachschauen.
Der erste Parameter ist der Name der Datei. Hier wird einfach "FileName" übergeben. der fünfte Parameter beschreibt, wie bereits oben beschrieben, wie die
Funktion arbeiten soll. Hier wird "Creation" übergeben.
Ist die Datei nach dieser Funktion nicht geöffnet ist wohl etwas schief gelaufen und die Funktion gibt "false" zurück.
Write
So, nun wird die Klasse endlich etwas leisten: Daten werden in eine Datei geschrieben.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
bool File::Write(const void* Data, unsigned int Size, unsigned long* WrittenBytes)
{
unsigned long TemporaryWrittenBytes = 0;
if (Data == NULL)
return false;
if (IsOpen() == false)
return false;
if (WriteFile(FileHandle, Data, Size, &TemporaryWrittenBytes, NULL) == FALSE)
return false;
if (TemporaryWrittenBytes != Size)
return false;
if (WrittenBytes != NULL)
*WrittenBytes = TemporaryWrittenBytes;
return true;
}
|
Der erste Parameter ist ein Zeiger auf das zu Schreibende. Man übergibt einfach die Adresse einer beliebigen Variable, Struktur oder Klasse. Der zweite
Paramter erwartet die Größe der zu schreibenden Daten in Bytes, meist genügt hier die Verwendung von "sizeof". Der dritte Parameter wiederum ist ein
Zeiger auf eine Variable die mit der Anzahl der tatsächlich geschriebenen Bytes gefüllt wird. Wird diese Information nicht benötigt kann dieser Parameter
NULL sein, was auch der Standardwert ist.
Mit der Prüfung von "Data" auf NULL wird überprüft ob überhaupt etwas zum Schreiben vorhanden ist. Wenn nicht wird "false" zurückgegeben. Danach folgt
wie immer "IsOpen".
Geschrieben werden die Daten mit "WriteFile". Der erste Parameter dieser Funktion ist das Handle. Hier wird natürlich "FileHandle" übergeben.
Der zweite Parameter sind die Daten die geschrieben werden soll, der dritte Paramter erwartet dann deren Größe in Bytes. Hier wird zuerst "Data" und
danach "Size" übergeben. Dem vierten Paramter wird die Variable "TemporaryWrittenBytes" übergeben um die Zahl der tatsächlich geschriebenen Bytes zu
erhalten. Hier wird nicht direkt "WrittenBytes" übergeben, da man ja nicht weiß ob "WrittenBytes" NULL ist oder nicht. Der fünfte Parameter wird auf
NULL gesetzt. Wer etwas über ihn erfahren möchte kann in der MSDN nachschauen.
Nach dem Schreibvorgang wird überprüft ob die Anzahl der Bytes die geschrieben werden sollten auch der Anzahl der Bytes die tatsächlich geschrieben
wurden entspricht. Ist dies nicht der Fall konnte nicht alles korrekt geschrieben werden und es wird "false" zurückgegeben.
Zum Schluss wird noch überprüft ob "WrittenBytes" NULL ist. Ist dem nicht so wird der Wert von "TemporaryWrittenBytes" in diese Variable übertragen.
Read
Hier gibt es kaum Unterschiede zu "Write".
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
bool File::Read(void* Data, unsigned int Size, unsigned long* ReadBytes)
{
unsigned long TemporaryReadBytes = 0;
if (Data == NULL)
return false;
if (IsOpen() == false)
return false;
if (ReadFile(FileHandle, Data, Size, &TemporaryReadBytes, NULL) == FALSE)
return false;
if (TemporaryReadBytes != Size)
return false;
if (ReadBytes != NULL)
*ReadBytes = TemporaryReadBytes;
return true;
}
|
Tatsächlich sieht der Ablauf genau gleich aus, mit dem Unterschied dass einige Variablen anders heißen, sowie die wichtigste Funktion natürlich nicht
"WriteFile" sondern "ReadFile" heißt.
GetSize
Mit der "GetSize"-Methode erhält man die Größe der geöffneten Datei.
|
C-/C++-Quelltext
|
1
2
3
4
|
unsigned long File::GetSize(void) const
{
return GetFileSize(FileHandle, NULL);
}
|
Hier wird "GetFileSize" verwendet um an die Größe der Datei zu kommen. Der erste Paramter ist das Handle, der zweite wird auf NULL gesetzt.
Wer wissen möchte wozu dieser gut ist weiß wo er nachschauen soll
SetPointerPosition
Der sogenannte Filepointer zeigt an, ab welcher Stelle der Datei geschrieben oder gelesen werden soll. Zu beginn befindet er sich an der Position 0 in
der Datei und "wandert" immer soviele Bytes weiter wie gerade geschrieben wurden. Demnach ist seine Position standardmäßig beim letzten Byte.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
bool File::SetPointerPosition(long Distance, unsigned long StartingPoint)
{
if (IsOpen() == false)
return false;
if (Distance > static_cast<long>(GetSize()))
return false;
//"GetPointerPosition" wird später implementiert
if (StartingPoint == FILE_CURRENT && (GetPointerPosition() + Distance) > static_cast<long>(GetSize()))
return false;
//Hier wird nun der Filepointer gesetzt
SetFilePointer(FileHandle, Distance, NULL, StartingPoint);
if (GetLastError() != NO_ERROR)
return false;
return true;
}
|
Der erste Paramter ist die Distanz die der Filepointer zurücklegen soll in Bytes. Jetzt stellt sich allerdings die Frage, von wo aus er sich bewegen soll.
Dies wird mit dem zweiten Parameter festgelegt. Auch hier definiert die WinAPI einige Möglichkeiten:
FILE_BEGIN
Der Pointer wird von der Position 0 aus bewegt.
FILE_CURRENT
Der Pointer wird von der aktuellen Position aus bewegt.
FILE_END
Der Pointer wird vom letzten Byte aus nach vorne hin bewegt.
In der Methode wird zuerst mal wieder geprüft ob die Datei geöffnet ist, ist sie das nicht wird wie immer "false" zurückgegeben. Nun wird überprüft
ob die Distanz größer als die Dateigröße ist. Wenn dies der Fall ist wird "false" zurückgegeben. Nun folgt eine weitere Abfrage die
erstmal überprüft ob die Startposition des Filepointers den Wert "FiLE_CURRENT" hat. Ist dies der Fall wird überprüft ob der Filepointer eine
Position nach dem letzten Byte annehmen würde nachdem er die Distanz zurückgelegt hat. Ist dies der Fall wird "false" zurückgegeben.
Schließlich kommt die SetFilePointer-Funktion an die Reihe. Diese erwartet zuerst das Handle der Datei, dann die Distanz die der Filepointer zurücklegen
soll. Der dritte Parameter kann in der MSDN nachgeschaut werden, der 4. ist die Startposition des Filepointers.
Zu guter letzt wird noch per "GetLastError" überprüft ob ein Fehler aufgetreten ist. Ist dies der Fall wird "false" zurückgegeben, andernfalls
ist die Methode hiermit erfolgreich am Ende angelangt.
GetPointerPosition
Nun folgt die Methode "GetPointerPosition", ohne die "SetPointerPosition" garnicht funktionsfähig ist, außer wenn man die Sicherheitsabfrage ausbaut.
Die WinAPI stelltr keine seperate Funktion zum erhalten der aktuellen Position des Filepointers zur verfügung, weshalb man auch dies mithilfe von
"SetFilePointer" erledigt. Der Rückgabewert eben jener Funktion ist die Position des Filepointers nach der Bewegung. Nun gibt man einfach an dass man den
Filepointer um 0 Bytes von der aktuellen Position aus bewegt. Der Filepointer bewegt sich dadurch nicht, die Variable wird aber natürlich trotzdem gefüllt.
Und schon hat man die aktuelle Filepointer-Position
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
long File::GetPointerPosition(void) const
{
long Position = 0;
if (IsOpen() == false)
return -1;
Position = SetFilePointer(FileHandle, 0, NULL, FILE_CURRENT);
if (GetLastError() != NO_ERROR)
return -1;
return Position;
}
|
Ist die Datei nicht geöffnet wird "-1" zurückgegeben. Da der Filepointer keine negative Position haben kann eignet sich dieser Wert als Fehlercode.
Danach wird wie oben bereits beschrieben "SetFilePointer" aufgerufen und mit "GetLastError" auf Erfolg geprüft. zum Schluss wird die aktuelle Position
des Filepointers zurückgegeben.
Beispielprogramm
Hier noch ein kleines Beispiel Programm welches die Verwendung der Klasse demonstriert.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
#include <windows.h>
#include <iostream>
//An dieser Stelle steht die File-Klasse
int main(void)
{
//Variablen
unsigned int i = 4;
unsigned int j = 8;
File f;
//Variablenwerte ausgeben
std::wcout << L"Wert 1 (Standard): " << i << std::endl;
std::wcout << L"Wert 2 (Standard): " << j << std::endl;
//Datei erstellen
f.Open(TEXT("Datei.file"), CREATE_ALWAYS);
//Variablen hintereinander in die Datei schreiben
f.Write(&i, sizeof(unsigned int));
f.Write(&j, sizeof(unsigned int));
//Datei schließen
f.Close();
//Variablen auf 0 setzen
i = j = 0;
//Variable ausgeben nach dem Nullen ausgeben
std::wcout << L"Wert 1 (Nach dem Nullen): " << i << std::endl;
std::wcout << L"Wert 2 (Nach dem Nullen): " << j << std::endl;
//Datei öffnen
f.Open(TEXT("Datei.file"), OPEN_EXISTING);
//Den Filepointer 4 Bytes weiterverschieben und "j" auslesen
f.SetPointerPosition(4, FILE_BEGIN);
f.Read(&j, sizeof(unsigned int));
//Den Filepointer auf Position 0 verschieben und "i" auslesen
f.SetPointerPosition(0, FILE_BEGIN);
f.Read(&i, sizeof(unsigned int));
//Variablenwerte ausgeben
std::wcout << L"Wert 1 (nach dem Lesen aus der Datei): " << i << std::endl;
std::wcout << L"Wert 2 (nach dem Lesen aus der Datei): " << j << std::endl;
//Datei schließen
f.Close();
//5 Sekunden warten damit man Zeit hat die Textmeldungen zu lesen ;)
Sleep(5000);
return 0;
}
|
Wie man bei der Implementierung gesehen hat geben alle Funktionen "true" oder "false" - nach nach Erfolg - zurück. Dies zu nutzen habe ich mir hier jetzt
gespart, natürlich sollte man es aber sonst immer nutzen.
Das Programm speichert einfach zwei Variablen in eine Datei, setzt die Variable auf 0 und liest sie dann in umgekehrter Reihenfolge einzeln wieder aus,
um die Funktionsweise von "SetPointerPosition" zu demonstrieren.
Links
Code
MSDN