Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

Pliki

[lekcja] Rozdział 4. Otwieranie, odczytywanie i zapisywanie, a także przenoszenie, kopiowanie czy usuwanie plików. Rozdział opisuje sposób uzyskania szczegółowych informacji o pliku, takich jak data ostatniej modyfikacji, czy też numer seryjny woluminu, do którego plik należy. Zawarto także informacje o odczytywaniu pojemności dysku.

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:
C/C++
CreateFile( lpFileName, dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDistribution, dwFlagsAndAttributes, hTemplateFile )

ArgumentZnaczenie
lpFileNameNazwa pliku
dwDesiredAccessTryb dostępu (odczyt lub zapis)
dwShareModeTryb współdzielenia pliku (jakie operacje mogą wykonywać na nim inne procesy
lpSecurityAttributesAtrybuty bezpieczeństwa (nie interesuje nas to :-) )
dwCreationDistributionJak utworzyć plik
dwFlagsAndAttributesFlagi i atrybuty
hTemplateFileUchwyt pliku, którego atrybuty chcemy skopiować na właśnie otwierany plik
 
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:
 
StałaZnaczenie
CREATE_NEWTworzy nowy plik, generuje błąd jeśli plik o podanej nazwie już istnieje.
CREATE_ALWAYSTworzy nowy plik, jeśli plik o podanej nazwie już istnieje, to zostaje on nadpisany.
OPEN_EXISTINGOtwiera istniejący plik, jeśli plik nie istnieje, to generuje błąd.
OPEN_ALWAYSOtwiera istniejący plik, jeśli plik nie istnieje, to go tworzy.
TRUNCATE_EXISTINGOtwiera istniejący plik, zerując go (obcinając do zerowej długości), jeśli plik nie istnieje, to funkcja generuje błąd. Plik musi być otwarty co najmniej z dostępem GENERIC_WRITE.
 
Następnie - flagi, czyli argument dwFlagsAndAttributes. Tutaj możliwości mamy dość sporo, między innymi takie:
 
StałaZnaczenie
FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_NORMAL itd.Atrybuty, jakie ma mieć plik, jeśli tworzymy nowy. Dokładny spis niżej, w rozdziale o atrybutach.
FILE_FLAG_RANDOM_ACCESSOkreśla, że plik ma dostęp swobodny (patrz niżej, rozdział o wskaźnikach pliku). System używa tego tylko jako wskazówki dla optymalizacji zapisu/odczytu.
FILE_FLAG_SEQUENTIAL_SCANOkreśla, że plik ma dostęp sekwencyjny, od początku do końca pliku (patrz niżej, rozdział o wskaźnikach pliku). System używa tego tylko jako wskazówki dla optymalizacji zapisu/odczytu.
FILE_FLAG_DELETE_ON_CLOSEOkreśla, że plik zostanie skasowany bezpośrednio po zamknięciu wszystkich odwołujących się do niego uchwytów (nie tylko tego uchwytu, dla którego ustawiona jest ta flaga).

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:
 
C/C++
DWORD dwSize = GetFileSize( hPlik, NULL );
if( dwSize == 0xFFFFFFFF ) // błąd!
     MessageBox( NULL, "Zły rozmiar pliku!", "Błąd", MB_ICONEXCLAMATION );
else
// ok, możemy wczytywać plik

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ć:

C/C++
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:
C/C++
ReadFile( hFile, lpBuffer, nBytesToRead, lpBytesRead, lpOverlapped )


ArgumentZnaczenie
hFileUchwyt pliku do odczytu
lpBufferWskaźnik na bufor, do którego czytamy dane
nBytesToReadLiczba bajtów do odczytania
lpBytesReadAdres zmiennej, otrzymującej liczbę bajtów, które udało się przeczytać
lpOverlappedAdres struktury, otrzymującej dane

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:
 
C/C++
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 ); // Zakończ program
}

dwRozmiar = GetFileSize( hPlik, NULL );
if( dwRozmiar == 0xFFFFFFFF ) {
    MessageBox( NULL, "Nieprawidłowy rozmiar pliku!", "Niedobrze...", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 ); // Zakończ program
}

Bufor =( LPSTR ) GlobalAlloc( GPTR, dwRozmiar + 1 );
if( Bufor == NULL ) {
    MessageBox( NULL, "Za mało pamięci!", "Ale wiocha...", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 ); // Zakończ program
}

if( !ReadFile( hPlik, Bufor, dwRozmiar, & dwPrzeczyt, NULL ) ) {
    MessageBox( NULL, "Błąd czytania z pliku", "Dupa blada!", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 ); // Zakończ program
}

Bufor[ dwRozmiar ] = 0; // dodaj zero na końcu stringa
SetWindowText( hwnd, Bufor ); // zrób coś z tekstem, np. wyświetl go

GlobalFree( Bufor ); // Zwolnij bufor
CloseHandle( hPlik ); // Zamknij plik

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:

C/C++
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 ); // Zakończ program
}

dwRozmiar = GetWindowTextLength( hwnd );
if( dwRozmiar == 0 ) {
    MessageBox( NULL, "Nieprawidłowy rozmiar pliku!", "Niedobrze...", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 ); // Zakończ program
}

Bufor =( LPSTR ) GlobalAlloc( GPTR, dwRozmiar + 1 );
if( Bufor == NULL ) {
    MessageBox( NULL, "Za mało pamięci!", "Ale wiocha...", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 ); // Zakończ program
}

GetWindowText( hwnd, Bufor, dwRozmiar ); // skopiuj do bufora tekst z jakiegoś okna
Bufor[ dwRozmiar ] = 0; // dodaj zero na końcu stringa

if( !WriteFile( hPlik, Bufor, dwRozmiar, & dwZapisane, NULL ) ) {
    MessageBox( NULL, "Błąd zapisu do pliku", "Dupa blada!", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 ); // Zakończ program
}

GlobalFree( Bufor ); // Zwolnij bufor
CloseHandle( hPlik ); // Zamknij plik

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):
 
StałaZnaczenie
FILE_ATTRIBUTE_ARCHIVEAtrybut 'Archiwalny' - oznacza się nim pliki lub katalogi, które poddane mają być backup'owi.
FILE_ATTRIBUTE_COMPRESSEDDla pliku oznacza, że dane w nim są skompresowane, dla katalogu - że wszystkie nowe pliki tworzone w tym katalogu mają być domyślnie kompresowane.
FILE_ATTRIBUTE_DIRECTORYCytując MSDN: Plik lub katalog jest katalogiem :-).
FILE_ATTRIBUTE_HIDDENPlik lub katalog jest ukryty.
FILE_ATTRIBUTE_NORMALTaki zupełnie zwyczajny plik lub katalog, bez żadnych innych atrybutów.
FILE_ATTRIBUTE_OFFLINEOznacza, że plik jest przechowywany offline i nie jest dostępny natychmiastowo.
FILE_ATTRIBUTE_READONLYPlik lub katalog jest przeznaczony tylko do odczytu, aplikacje nie mogą go modyfikować ani usunąć.
FILE_ATTRIBUTE_SYSTEMPlik lub katalog jest częścią systemu operacyjnego, czyli tylko kumple Billa lub prawdziwi hakerzy wiedzą, co jest w środku i jak w tym grzebać.
FILE_ATTRIBUTE_TEMPORARYPlik tymczasowy, powinien być usunięty natychmiast w momencie, kiedy przestaje być potrzebny.

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:
C/C++
GetFileTime( hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime )

ArgumentZnaczenie
hFileUchwyt pliku
lpCreationTimeWskaźnik do struktury, w której funkcja zapisze czas utworzenia pliku
lpLastAccessTimeWskaźnik do struktury, w której funkcja zapisze czas ostatniego dostępu do pliku
lpLastWriteTimeWskaźnik do struktury, w której funkcja zapisze czas ostatniej modyfikacji pliku

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:

C/C++
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):

C/C++
#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:
 
StałaZnaczenie
DRIVE_UNKNOWNNie można określić typu dysku
DRIVE_NO_ROOT_DIRPodano błędny argument (taki katalog główny nie istnieje)
DRIVE_REMOVABLEDysk wymienny
DRIVE_FIXEDDysk stały (zwykły twardy dysk)
DRIVE_REMOTEDysk sieciowy
DRIVE_CDROMTo ci dopiero zagadka ;-)
DRIVE_RAMDISKRAM-dysk
 
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:
C/C++
GetDiskFreeSpace( lpRootPathName, lpSectorsPerCluster, lpBytesPerSector, lpNumberOfFreeClusters, lpTotalNumberOfClusters )

ArgumentZnaczenie
lpRootPathNameKatalog główny dysku (literka dysku, wraz z dwukropkiem i ewentualnie beksleszem)
lpSectorsPerClusterdres zmiennej, do której funkcja zapisze liczbę sektorów w 1 klastrze
lpBytesPerSectorAdres zmiennej, do której funkcja zapisze liczbę bajtów w 1 sektorze
lpNumberOfFreeClustersAdres zmiennej, do której funkcja zapisze liczbę wolnych klastrów
lpTotalNumberOfClustersAdres zmiennej, do której funkcja zapisze liczbę wszystkich klastrów na dysku
 
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:

C/C++
#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):

C/C++
#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:
C/C++
CopyFile( lpSrcFileName, lpDstFileName, bFailIfExists )

ArgumentZnaczenie
lpSrcFileNameNazwa pliku (może być ze ścieżką)
lpDstFileNameNazwa pliku, do którego kopiujemy (może zawierać inną ścieżkę, niż ścieżka do pliku żródłowego)
bFailIfExistsFlaga określająca co robić, jeśli podany plik istnieje

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:

C/C++
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:
C/C++
SetFilePointer( hFile, lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod );

ArgumentZnaczenie
hFileUchwyt pliku
lDistanceToMoveLiczba bajtów, o które chcemy przesunąć wskaźnik
lpDistanceToMoveHighAdres górnego słowa dystansu, o który przesuwamy wskaźnik
dwMoveMethodSposób w jaki przesuwamy wskaźnik (tj. względem czego to robimy)

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.
 
StałaZnaczenie
FILE_BEGINPrzesuwamy wskaźnik względem początku pliku (podana wartość DistanceToMove jest de facto nowym położeniem wskaźnika)
FILE_CURRENTPrzesuwamy wskaźnik względem jego aktualnej pozycji
FILE_ENDPrzesuwamy wskaźnik względem końca pliku

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?
Poprzedni dokument Następny dokument
Obsługa myszy i klawiatury Grafika