Odczyt z pliku
Marny los programisty, który nie umie odczytywać i zapisywać danych do pliku. Dlatego warto by się w ten temat zagłębić. Zrobimy to krótko i rzeczowo. Najpierw, żeby cokolwiek z plikiem zrobić, musimy go otworzyć. Funkcji do wyboru mamy od czorta, ale najlepiej będzie skorzystać z
CreateFile. Nazwa może niezbyt trafna, ale w każdym razie funkcja ta robi, co trzeba:
Składnia:
CreateFile( lpFileName, dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDistribution, dwFlagsAndAttributes, hTemplateFile )
Pierwszy argument, to oczywiście nazwa pliku, który chcemy otworzyć. Pamiętaj o podwójnych beksleszach
\\, jeśli zawiera ona ścieżkę dostępu. Argument drugi określa rodzaj dostępu: odczyt lub zapis. Na razie chcemy czytać, więc ustawiamy go na
GENERIC_READ.
Następny argument to tryb współdzielenia pliku - w systemie Windows kilka programów może mieć jednocześnie dostęp do jednego pliku, ale nie zawsze taka synchronizacja jest możliwa. Jeśli nie chcemy, by jakiś inny program grzebał w naszym pliku, dajemy w tym argumencie
0. W przeciwnym wypadku możemy ustawić
FILE_SHARE_READ (jeśli chcemy, by inne programy mogły równocześnie odczytywać z naszego pliku dane),
FILE_SHARE_WRITE (zgadnij) lub
FILE_SHARE_READ | FILE_SHARE_WRITE, czyli pełny dostęp. Najbezpieczniej będzie jednak ustawić
0.
Argument
lpSecurityAttributes na razie nas nie obchodzi, niech się wypcha sianem (czyli
NULL-em). Dużo ważniejszy jest następny argument,
dwCreationDistribution. Określa on, co stanie się podczas otwierania w zależności od tego, czy plik istnieje czy też nie. Musimy wybrać jedną z następujących stałych:
Następnie - flagi, czyli argument
dwFlagsAndAttributes. Tutaj możliwości mamy dość sporo, między innymi takie:
Ostatni argument musi być równy
NULL, jeśli piszemy pod Windowsem 95 lub pokrewnym. W przeciwnym razie spowodujemy błąd, na czym raczej nam nie zależy, toteż posłuchamy grzecznie wskazań Microsoftu.
Uff, to już wszystko. Tyle właśnie trzeba zachodu, żeby otworzyć jeden głupi plik. No ale teraz już sytuacja jest w miarę klarowna. Funkcja
CreateFile, o ile się jej powiedzie zadanie, powinna nam zwrócić uchwyt do pliku, który jest typu
HANDLE. W przeciwnym wypadku zwróci wartość
INVALID_HANDLE_VALUE.
Załóżmy, że chcemy sobie cały nasz pliczek wczytać do jakiegoś wcześniej zaalokowanego bufora znakowego. Można to zrobić na co najmniej dwa sposoby: wczytywać plik bajt po bajcie, aż napotkamy znak '''EOF''' (End Of File), co jednak będzie wykonywane w żółwim tempie, albo najpierw pobrać rozmiar pliku, a następnie od razu wczytać wszystkie bajty do bufora. Wybieramy oczywiście rozwiązanie nr 2.
Funkcja zwracająca rozmiar pliku nazywa się (co za niespodzianka)
GetFileSize. Zwracana wartość jest typu
DWORD, drugi argument funkcji (bo dwa są) zostawmy na razie w spokoju:
DWORD dwSize = GetFileSize( hPlik, NULL );
if( dwSize == 0xFFFFFFFF )
MessageBox( NULL, "Zły rozmiar pliku!", "Błąd", MB_ICONEXCLAMATION );
else
Jak nietrudno obliczyć, zmienna typu
DWORD może przedstawić rozmiar pliku tylko wtedy, gdy jest on mniejszy niż jakieś 4 GB, co powinno nam w większości wypadków wystarczyć ;-). W razie czego jednak funkcja
GetFileSize potrafi pobrać również rozmiar znacznie większych plików i do tego właśnie służy ten drugi argument, gdzie wstawiliśmy
NULL. Jak to zrobić, tego nie pokażę bo i tak nam się nie powinno przydać ;-P. Jeśli kogoś będzie bardzo ciekawość dręczyć, może spytać w mejlu.
Mamy więc plik, znamy jego rozmiar, w tym momencie możemy sobie alokować jakiś zgrabny buforek. W "gołym" C++ robiliśmy to operatorem
new, teraz poznamy drugi sposób, zalecany dla okienkowych aplikacji. Jest to funkcja
GlobalAlloc (lub
LocalAlloc, która w Win95 robi dokładnie to samo). Oto jak z niej korzystać:
LPSTR Bufor;
Bufor =( LPSTR ) GlobalAlloc( GPTR, dwSize );
if( Bufor == NULL )
MessageBox( NULL, "Błąd alokacji", "Ups...", MB_ICONSTOP );
GlobalAlloc zwraca wskaźnik do świeżo alokowanego obszaru, podobnie jak
new. Musimy jednak przekonwertować jawnie ten wskaźnik na taki typ, jakiego potrzebujemy (tutaj z kolei mamy podobieństwo do
malloc-a). Pierwszy argument funkcji, czyli stała
GPTR, sprawia, że nowo alokowany obszar zostaje od razu wypełniony zerami (co daje tej funkcji niejaką przewagę nad
new). Jeśli tego nie chcemy, zamieniamy
GPTR na
GMEM_FIXED. Jeśli funkcja zwróci
NULL, oznacza to oczywiście, że mamy w systemie deficyt pamięciowy i w związku z tym nowe obszary przydzielane są tylko na kartki ;-).
Przed nami ostatni, najważniejszy etap - właściwe czytanie z pliku. Posłużymy się w tym celu funkcją (znów niespodzianka)
ReadFile. Jej składnia wygląda tak:
Składnia:
ReadFile( hFile, lpBuffer, nBytesToRead, lpBytesRead, lpOverlapped )
Wyjaśnienia wymagają chyba tylko ostatnie dwa argumenty. Otóż
lpNumberOfBytesRead to adres zmiennej, do której funkcja wpisze liczbę bajtów, jaką uda jej się przeczytać. Nie musimy z tej zmiennej korzystać, ale podanie jej adresu jest wymagane. Chyba, że ostatni argument funkcji jest różny od
NULL, ale nie będzie, bo i po co ;-).
NULL tak bardzo ułatwia nam życie :-).
Jeśli dysk nie zardzewiał, nikt nie skasował nam pliku kiedy nie patrzyliśmy, nie mamy akurat wirusa, napięcie w sieci nie wzrasta powyżej jakichś 400 V i nie spada poniżej 200, w pokoju panuje temperatura, w której komputerowe akcesoria się nie topią ani nie zamarzają i wszystkie inne warunki są sprzyjające, to dane z pliku zostaną wczytane i funkcja zwróci wartość niezerową. Pozostaje nam wtedy jeszcze sprawdzić, czy liczba przeczytanych bajtów, czyli wartość zmiennej pod adresem
lpNumberOfBytesRead, jest równa liczbie bajtów, które funkcja MIAŁA przeczytać. Jeśli tak jest, to wszystko OK, jeśli nie - cóż, niewesoło, ale żeby nie zapeszyć sytuacji takich nie będziemy tutaj omawiać ;-).
Pora wszystko, co powiedziane zostało powyżej, pokazać na konkretnym przykładzie. Oto wczytywanie pliku do bufora:
LPSTR Bufor;
DWORD dwRozmiar, dwPrzeczyt;
HANDLE hPlik;
hPlik = CreateFile( "test.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL );
if( hPlik == INVALID_HANDLE_VALUE ) {
MessageBox( NULL, "Nie można otworzyć pliku.", "A to pech!", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
dwRozmiar = GetFileSize( hPlik, NULL );
if( dwRozmiar == 0xFFFFFFFF ) {
MessageBox( NULL, "Nieprawidłowy rozmiar pliku!", "Niedobrze...", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
Bufor =( LPSTR ) GlobalAlloc( GPTR, dwRozmiar + 1 );
if( Bufor == NULL ) {
MessageBox( NULL, "Za mało pamięci!", "Ale wiocha...", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
if( !ReadFile( hPlik, Bufor, dwRozmiar, & dwPrzeczyt, NULL ) ) {
MessageBox( NULL, "Błąd czytania z pliku", "Dupa blada!", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
Bufor[ dwRozmiar ] = 0;
SetWindowText( hwnd, Bufor );
GlobalFree( Bufor );
CloseHandle( hPlik );
Jak widać, w przykładzie aż roi się od rozmaitych
if-ów, które pilnują, żeby nam jakiś straszliwy error się nie zdarzył. Zabawa z plikami to bowiem jak mecz piłki nożnej na polu minowym - gdzie się nie ruszysz, tam coś można spieprzyć, dlatego należy zachować najwyższą ostrożność.
Zapis do pliku
Ktoś musi zapisywać, by odczytywać mógł ktoś. No więc teraz pokażemy sobie, jak wrzucić dane do pliku. Stwórzmy sobie jakieś pole tekstowe, w które będzie można powpisywać jakieś głupoty, które potem powędrują do pliku. Generalnie zasady są bardzo podobne do odczytu, tzn. plik trzeba najpierw otworzyć. Tym razem użyjemy flagi
CREATE_ALWAYS, która... Nie, nie tworzy podpaski ze skrzydełkami. Jeśli ustawimy tę flagę, będzie utworzony nowy plik, a jeśli plik o podanej nazwie już istnieje, zostanie wyzerowany.
Jak się zapewne domyślasz, trzeba też będzie zamienić flagę
GENERIC_READ na
GENERIC_WRITE. Wreszcie, należy uwzględnić współdzielenie pliku - generalnie najbezpieczniej nie dawać innym procesom dostępu do pliku, kiedy akurat coś do niego zapisujemy, więc ustawiamy odpowiedni argument na
0.
Pobieranie rozmiaru pliku tym razem nam odpada, ze zrozumiałych powodów. Bufor na tekst nadal jednak musimy sobie utworzyć. Skopiujemy do niego tekst z pola tekstowego przy pomocy funkcji
GetWindowText. Zanim jednak skopiujemy i zanim utworzymy bufor, musimy znać długość tekstu. I tutaj z kolei przyda nam się funkcja
GetWindowTextLength.
Funkcja zapisująca dane do pliku nazywa się oczywiście
WriteFile i ma właściwie identyczną składnię co
ReadFile. Dlatego też nie będę się powtarzał i przejdę od razu do przykładu:
LPSTR Bufor;
DWORD dwRozmiar, dwZapisane;
HANDLE hPlik;
hPlik = CreateFile( "test.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
if( hPlik == INVALID_HANDLE_VALUE ) {
MessageBox( NULL, "Nie można otworzyć pliku.", "A to pech!", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
dwRozmiar = GetWindowTextLength( hwnd );
if( dwRozmiar == 0 ) {
MessageBox( NULL, "Nieprawidłowy rozmiar pliku!", "Niedobrze...", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
Bufor =( LPSTR ) GlobalAlloc( GPTR, dwRozmiar + 1 );
if( Bufor == NULL ) {
MessageBox( NULL, "Za mało pamięci!", "Ale wiocha...", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
GetWindowText( hwnd, Bufor, dwRozmiar );
Bufor[ dwRozmiar ] = 0;
if( !WriteFile( hPlik, Bufor, dwRozmiar, & dwZapisane, NULL ) ) {
MessageBox( NULL, "Błąd zapisu do pliku", "Dupa blada!", MB_ICONEXCLAMATION );
PostQuitMessage( 0 );
}
GlobalFree( Bufor );
CloseHandle( hPlik );
Pomocne funkcje
Atrybuty plików
Możemy łatwo sprawdzić atrybuty pliku o podanej nazwie, wykorzystując funkcję
GetFileAttributes. Zwraca ona liczbę typu
DWORD. Może ona być równa
0xFFFFFFFF (co nie oznacza nic dobrego), albo też przyjąć jedną z następujących wartości (mogą one być łączone):
Dla wścibskich: tak, istnieje funkcja
SetFileAttributes. Pobiera ona dwa argumenty, tj. nazwę pliku i zmienną typu
DWORD, zawierającą dowolną (prawie) kombinację powyższych stałych, zależnie jakie atrybuty chcemy ustawić. Zwraca
true jeśli się uda,
false jeśli nie. Nie można ustawiać atrybutu
FILE_ATTRIBUTE_COMPRESSED (tzn. błąd się nie pojawi, ale też taka operacja nie skompresuje pliku).
Data i czas pliku
Czasami może się przydać informacja, kiedy dany plik został utworzony, zmodyfikowany lub w ogóle ostatnio używany. I na pewno nie zgadniesz, jaka funkcja zdobędzie dla nas taką informację - ano,
GetFileTime. W przeciwieństwie do funkcji pobierającej lub ustawiającej atrybuty, ta może operować wyłącznie na plikach już otwartych, i to, co więcj, otwartych do odczytu (
GENERIC_READ).
Składnia jest taka:
GetFileTime( hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime )
Pierwszy argument jest oczywisty, trzy pozostałe to wskaźniki do struktur typu
FILETIME, przechowujących informację o dokładnej dacie pliku. Jest to struktura
64-bitowa (dwa
32-bitowe pola typu
DWORD). W MSDN natknąłem się na informację, że może pomieścić daty od 1601 roku, ale chyba im się ta szóstka z dziewiątką pomyliła, bo po co komu XVII wiek w komputerze ;-). W tym samym miejscu twierdzą (co już bardziej prawdopodobne), że dokładność czasu przechowywanego w tej strukturze to 100 nanosekund.
Nie musimy deklarować wszystkich trzech struktur na raz, jeśli np. potrzebna nam tylko data ostatniej modyfikacji, to podajemy tylko
lpLastWriteTime, pozostałe dwa wskaźniki ustawiamy na
NULL.
Jak użyć danych ze struktury
FILETIME? Najlepiej przekonwertować ją na czas systemowy funkcją
FileTimeToSystemTime. Pierwszy jej argument to wskaźnik do struktury typu
FILETIME, drugi - do struktury typu
SYSTEMTIME. Wartość zwracana: jak zwykle, czyli
0 oznacza błąd, wartość niezerowa - powodzenie. Struktura
SYSTEMTIME jest już wcale przyjazna w użyciu, jej pola to:
wYear, wMonth, wDayOfWeek, wDay, wHour, wMinute, wSecond, wMiliseconds. Chyba nie trzeba więcej tłumaczyć... No, może tylko, że wartości wMonth zaczynają się od
1 (co oczywiście oznacza styczeń), natomiast
DayOfWeek przyjmuje wartości
0-6, gdzie
0 to niedziela,
1 poniedziałek itd.
Chcesz sobie zmienić datę modyfikacji pliku nie ruszając zawartych w nim danych ani daty systemowej? Nie ma sprawy, pobaw się funkcją
SetFileTime (składnia analogiczna do
GetFileTime). Przykładów nie podaję, żeby pobudzić twoją kreatywność ;-).
Rozszerzone informacje
Jest taka jedna funkcja-gigant, pobierająca naraz informację o atrybutach pliku, wszystkich datach, rozmiarze i jeszcze kilku innych rzeczach. Zowie się toto
GetFileInformationByHandle. Prawda, że niezły potworek? Składnia na szczęście jest bardzo prosta: pierwszy argument to uchwyt pliku, drugi - wskaźnik na strukturę typu, tu głęboki wdech,
BY_HANDLE_FILE_INFORMATION. Składniki tej struktury to:
typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks;
DWORD nFileIndexHigh;
DWORD nFileIndexLow;
} BY_HANDLE_FILE_INFORMATION;
Jak widać, oprócz wspomnianych rzeczy, możemy jeszcze uzyskać: numer seryjny woluminu, do którego przynależy nasz plik, liczbę skrótów, jakie się do niego odwołują oraz unikalną liczbę związaną z plikiem. Mało przydatne, ale fajne bajerki ;-).
Informacje o dysku
Przyda się jeszcze coś dla twórców wirusów :-). Najpierw dowiemy się, jak sprawdzić, jakie dyski są dostępne w komputerze. To z kolei rozpoczniemy od ustalenia, jakie literki-określenia dysków są w ogóle prawidłowe. Służy do tego funkcja
GetLogicalDrives. Nie ma ona żadnych argumentów, a zwraca wartość typu
DWORD, której kolejne bity wskazują, czy dana literka jest określeniem dysku (pierwszy bit - A, drugi - B itd.). Jeśli wartość zwrócona wynosi
0, to, jak się domyślamy, nic dobrego ;-).
Jeśli nie chce nam się grzebać w bitach, mamy alternatywę w postaci funkcji
GetLogicalDriveStrings. Jeśli podamy jej rozmiar i adres bufora znaków, to ona wypełni go wszystkimi dostępnymi literkami dysków, porozdzielanymi przez znaki zerowe, a na końcu tej wyliczanki postawi podwójny znak zerowy. Oto jak wykorzystać taki buforek do wypisania wszystkich literek na ekranie (przykład wymaga włączenia konsoli w opcjach kompilatora):
#include <windows.h>
#include <iostream.h>
#include <stdlib.h>
const WORD nBufferSize = 40;
LPSTR Bufor =( LPSTR ) GlobalAlloc( GPTR, nBufferSize );
WORD Wymagane, i = 0, flaga = 0;
Wymagane = GetLogicalDriveStrings( nBufferSize, Bufor );
if( Wymagane > nBufferSize ) {
cout << "Za maly bufor, potrzeba " << Wymagane << " bajtow." << endl;
system( "pause" );
return 1;
}
cout << "Dostepne dyski:" << endl;
do {
koniec = 0;
if( Bufor[ i ] != 0 ) cout << Bufor[ i ];
else { cout << endl; flaga = 0; }
} while( Bufor[ ++i ] != 0 || flaga );
GlobalFree( Bufor );
system( "pause" );
Skoro mamy już literkę danego dysku, możemy się jeszcze dowiedzieć, jakiego typu jest to dysk (stały, wymienny, CD-ROM, dysk sieciowy, RAM-dysk...). Załatwimy to funkcją
GetDriveType. Ma ona tylko jeden argument, którym jest katalog główny naszego dysku, czyli właśnie literka, którą uzyskaliśmy w powyższym przykładzie z dwukropkiem na końcu, może też być jeszcze z beksleszem.
GetDriveType zwraca jedną z następujących wartości:
Oczywiście stacje dyskietek podchodzą pod
DRIVE_REMOVABLE, a nagrywarki - pod
DRIVE_CDROM.
Sprawdźmy sobie jeszcze, ile mamy miejsca na dysku. Można to zrobić funkcją
GetDiskFreeSpace lub
GetDiskFreeSpaceEx. W bardzo starych Windowsach ta pierwsza może nawalić, jeśli zastosować ją do dysku większego niż 2 GB, ale na moim poczciwym 98 działa bez zarzutu.
Składnia:
GetDiskFreeSpace( lpRootPathName, lpSectorsPerCluster, lpBytesPerSector, lpNumberOfFreeClusters, lpTotalNumberOfClusters )
Pierwszy argument jest chyba oczywiste, cztery pozostałe to wskaźniki do 4 zmiennych typu
DWORD, do których funkcja wpisze odpowiednie informacje. Po wywołaniu funkcji i ewentualnym sprawdzeniu, czy zwróciła
true, możemy już skorzystać z wyników. Tylko po kiego diabła nam wiedzieć, ile klastrów ma nasz dysk? Chcemy bajty! Wiemy, ile sektorów przypada na 1 klaster, wiemy też, ile sektor ma bajtów, wreszcie wiemy, ile mamy wszystkich klastrów na dysku i ile z nich jest wolnych. Wystarczy więc pomnożyć odpowiednie zmienne i mamy, co chcieliśmy:
#include <windows.h>
#include <iostream.h>
#include <stdlib.h>
DWORD Sektory, Bajty, Wolne, Klastry;
if( GetDiskFreeSpace( "c:", & Sektory, & Bajty, & Wolne, & Klastry ) ) {
cout << "Dysk C: ma " << Sektory * Bajty * Klastry << " bajtow, z tego "
<< Sektory * Bajty * Wolne << " wolnych." << endl;
}
system( "pause" );
Wspomniana funkcja
GetDiskFreeSpaceEx jest trochę bardziej skomplikowana w użyciu, ponieważ wymaga Windowsa 95 w wersji '''OSR 2''', a więc trzeba przed jej wywołaniem sprawdzić wersję systemu, potem załadować bibliotekę kernel32.dll, następnie pobrać adres funkcji i dopiero wtedy ją wywołać. Szkoda tu miejsca na takie manewry, ale postaram się pokazać to przy innej okazji.
Kopiowanie, przenoszenie, usuwanie...
Teraz dla rozluźnienia pobawimy się w menedżera plików. Najpierw sprawdzimy, w jakim katalogu aktualnie jesteśmy. Funkcja
GetCurrentDirectory wymaga podania bufora wraz z maksymalnym rozmiarem (tak jak
GetLogicalDriveStrings):
#include <windows.h>
#include <iostream.h>
#include <stdlib.h>
const WORD dl = 255;
LPSTR Bufor =( LPSTR ) GlobalAlloc( GPTR, dl );
GetCurrentDirectory( dl, Bufor );
cout << "Jestesmy w " << Bufor << endl;
GlobalFree( Bufor );
system( "pause" );
W podobny sposób działają funkcje
GetWindowsDirectory oraz
GetSystemDirectory, przeznaczenia których bez wątpienia sam się domyślasz.
Kopiowaniem plików zajmuje się funkcja, jakże by inaczej,
CopyFile. Argumenty to:
Składnia:
CopyFile( lpSrcFileName, lpDstFileName, bFailIfExists )
Pierwszych dwóch nie muszę chyba omawiać. Ostatni to flaga określająca, co robić w wypadku, gdy plik docelowy już istnieje. Jeśli w takim wypadku ustawione jest
TRUE, to funkcja nawala (zwraca
0), natomiast jeśli
FALSE, to nadpisuje (niszczy) istniejący plik i zwraca wartość niezerową.
Przenoszenie lub zmiana nazwy plików to zadanie funkcji
MoveFile. Ta ma tylko dwa argumenty, takie same, jak dwa pierwsze funkcji
CopyFile. Ścieżka podana jako drugi argument NIE MOŻE istnieć w chwili wywołania funkcji. Możemy przenosić pliki na inny dysk, ale katalogi można przenosić tylko w obrębie tego samego dysku. Jeśli chcesz zastąpić istniejący plik, musisz użyć innej funkcji:
MoveFileEx( Plik, PlikDoZastapienia, MOVEFILE_REPLACE_EXISTING );
Kasowania plików mógłbyś się bez trudu domyślić sam - funkcja
DeleteFile, jedyny argument to nazwa pliku do skasowania. Równie proste jest tworzenie katalogów (funkcja
CreateDirectory, argumenty: nazwa,
NULL) oraz ich usuwanie (
RemoveDirectory, nazwa). Ta ostatnia usuwa jedynie puste katalogi, coś jak komenda rmdir w DOS-ie. Praca domowa - przećwiczyć samodzielnie ;-).
Wskaźnik pliku
Są dwa rodzaje dostępu do danych, w tym także danych w pliku: '''sekwencyjny''' i '''swobodny'''. Dostęp sekwencyjny oznacza, że musimy czytać wszystkie bajty pliku po kolei, aż dotrzemy do tego, który nas interesuje. Dostęp swobodny pozwala "przeskoczyć" zbędne bajty i odczytać od razu tę "właściwą" część pliku. Ułatwia nam to tzw.
wskaźnik pliku (file pointer). Nie ma on nic wspólnego ze wskaźnikami C++, po prostu jest to numer bajtu, na którym zakończyliśmy czytanie lub zapis. Jeśli czytamy sekwencyjnie, nie musimy się o niego martwić. Jeśli chcemy przestawić wskaźnik pliku, używamy funkcji
SetFilePointer:
Składnia:
SetFilePointer( hFile, lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod );
Argument
lpDistanceToMoveHigh możemy sobie darować (tzn. ustawić na
NULL), ale wtedy możemy operować na plikach mniejszych, niż jakieś 4 GB (patrz funkcja
GetFileSize na górze tej części kursu). W przeciwnym wypadku argument ten wskazuje na dodatkową zmienną typu
LONG, zawierającą resztę liczby (wtedy funkcja może sobie operować na plikach do
18446744 terabajtów (!)). Ostatni argument określa miejsce, względem którego liczymy bajty, o które przestawiamy wskaźnik.
Funkcja powinna zwrócić nową wartość wskaźnika, przy czym z oczywistych powodów tak naprawdę zwraca tylko podwójne słowo tej wartości (pierwszą połowę), co daje oczekiwany wynik tylko wtedy, kiedy ustawiliśmy
lpDistanceToMoveHigh na
NULL. Natomiast zwrócenie wartości
INVALID_SET_FILE_POINTER, równej
0xFFFFFFFF (czyli maksymalnej wartości dla typu
DWORD), oznacza błąd.
Tyle o plikach, gdyby ktoś chciał wiedzieć coś więcej, może mejlować - w końcu trzeba trochę zapełniać dział FAQ, no nie?