2 15 Zapis i odczyt danych (2)


Rozdział 15.
Zapis i odczyt danych


W tym rozdziale:

Klasa CString
Manipulowanie łańcuchami za pomocą klasy cstring
Klasa CFile
Praca z plikami
Klasa CCompressedFile
Praca ze skompresowanymi plikami
Klasa CSerial
Komunikacja łączem szeregowym
Klasa CRegistry
Praca z Rejestrem systemowym
Schowek
Praca ze schowkiem


Większość programów ma jakieś dane, które podczas pracy wykorzystuje, tworzy czy przekazuje. Dla procesorów tekstu danymi są dokumenty, które są zapisywane, odczytywane, drukowane czy sprawdzane pod względem poprawności. Arkusze kalkulacyjne nie tylko zapisują i odczytują dane, ale także wykonują na nich obliczenia. Nawet gry, choćby takie jak pasjans dostarczany wraz z Windows, przechowuj ą dane, traktując je jako zestaw kart.
MFC oferuje mnóstwo narzędzi przeznaczonych do pracy z danymi. W tym rozdziale omówimy klasy cstring, CFile a także wbudowaną w klasę cwnd obsługę systemowego schowka. Poznamy także kilka klas nie będących klasami MFC, ułatwiających posługiwanie się pewnymi innymi rodzajami danych. Jedną z takich klas jest klasa oferująca dostęp do portu szeregowego. Z naszą klasą CSerial możesz łączyć się poprzez modem z innym komputerem. Kolejną rzeczą pominiętą w MFC jest łatwy dostęp do Rejestru. Obecnie jest to zalecany sposób przechowywania informacji konfiguracyjnych dla aplikacji, więc jako programista dość często powinieneś mieć z Rejestrem do czynienia.
Klasa CString
Jeśli używałeś znakowych tablic języka C oraz funkcji biblioteki C przeznaczonych do manipulowania łańcuchami, możesz być bardzo zadowolony z wprowadzenia klasy cstring - możesz być, lecz nie musisz. Gdy pierwszy raz użyłem Visual C++w wersji l .5, byłem już doświadczonym programistą C. Prawie rok zajęło mi przyzwyczajenie się do korzystania z klasy cstring zamiast starych, dobrych tablic znaków języka C. Jeśli zaczynasz od razu od Visual C++, masz szczęście - nie będziesz musiał pozbywać się starych nawyków.
Do czegóż więc przydaje się ta "wspaniała-i-jedyna" klasa? Do dwóch rzeczy: przechowuje dane i udostępnia funkcje umożliwiające wykonywanie operacji dawniej wykonywanych za pomocą funkcji biblioteki C, takich jak strcpyo czy strcmpf). Nie musisz już deklarować (lub alokować) tablicy znaków, a następnie kopiować do niej łańcucha martwiąc się, że zabraknie w niej miejsca. Możesz dołączać do łańcucha dodatkowe znaki bez względu na rozmiar tablicy - o to, by bez względu na przeprowadzaną operację nie zabrakło w niej miejsca, zadba sama klasa cstring.
Tworzenie łańcuchów
Najprostszym sposobem przypisania łańcucha znaków klasie cstring jest operator =. Możesz używać go w odniesieniu do łańcucha znaków i innych obiektów cstring. Ilustrują to dwa poniższe przykłady:
Przykład 1. Przypisywanie łańcucha znaków z użyciem operatora =:
strText = "To jest tekst";
Przykład 2. Przypisywanie obiektu cstring z użyciem operatora =:
CString strCopy = strText;
Innym łatwym sposobem przypisania danych obiektom cstring jest użycie konstruktora klasy cstring. Po prostu przekaż łańcuch znaków lub obiekt cstring do konstruktora, a dane zostaną automatycznie skopiowane do nowo tworzonego obiektu. Przykłady:
Przykład 1. Przypisywanie łańcucha znaków w konstruktorze klasy cstring: CString strText1( "To jest pierwszy tekst" );
Przykład 2. Przypisywanie obiektu cstring w konstruktorze klasy cstring: CString strText2 ( strTextl );
W pewnych przypadkach chcemy zapewnić obsługę łańcuchów Unicode. W tym celu po prostu użyj makra _T. Poniższy przykład przypisuje łańcuch znaków (zarówno Unicode jak i zwykły) obiektowi cstring:
CString strText = _T( "Ten napis może być napisem Unicode." );
Kilka funkcji pomocniczych odnosi się do zawartości obiektu cstring. Są to funkcje GetLength (), isEmpty () oraz Empty (). Funkcja GetLength () zwraca długość łańcucha. Dla napisu to jest tekst funkcja zwróci wartość 13. Funkcja isEmptyO zwraca wartość TRUE, jeśli obiekt cstring nie posiada bufora danych lub bufor danych jest pusty. Jeśli obiekt cstring posiada łańcuch, na przykład to jest tekst, funkcja zwróci wartość FALSE. Z kolei funkcja Empty () usuwa dane z obiektu cstring. Tuż po wyczyszczeniu obiektu funkcją Empty (), funkcja isEmpty() zawsze zwraca wartość
TRUE.
Dostęp do łańcucha znaków
Do otrzymania wskaźnika do łańcucha znaków zawartego w obiekcie cstring służy funkcja GetBuf f er (). Ta funkcja otrzymuje argument w postaci liczby całkowitej określający minimalny rozmiar bufora (w znakach). Po wywołaniu funkcji GetBuf fer () i już po wykorzystaniu wskaźnika zaleca się późniejsze wywołanie funkcji ReleaseBuffer o . Poniższy kod pobiera wskaźnik do danych w obiekcie cstring, sprawdza go, po czym zwalnia bufor:
CString strText = "To jest tekst"; char *lpText = strText.GetBuffer( 20 }; if( lpText[3] == 'j' )
AfxMessageBox( "To są poprawne dane." ); else
AfxMessageBox( "Wskaźnik nie wskazuje poprawnych danych." ); strText. ReleaseBuffer ( ) ;
Do danych możesz odwoływać się także za pomocą funkcji GetAt () i setAt (). Funkcja GetAt () otrzymuje pojedynczy argument będący indeksem znaku, który chcesz pobrać i zwraca ten znak. Funkcja SetAt () otrzymuje indeks znaku oraz znak, który ma być ustawiony we wskazanym miejscu. Poniższy przykład pokazuje pobieranie i ustawianie znaków w obiektach cstring:
CString strText = "To jest inny tekst"; strText.SetAt(13, 'b');
// Teraz napis ma postać "To jest inny bekst" if( strText.GetAt( 3) == 'j' )
AfxMessageBox("No pewnie, ten znak to 'j'."); else
AfxMessageBox( "Pewnie wystąpił jakiś błąd!" );
Skrótem do funkcji GetAt () jest operator [ ], który działa po prostu jak inny zapis tej funkcji. Poniższy przykład wykorzystuje operator [ ] w celu sprawdzenia znaku w obiekcie
CString:
CString strText = "To jest jeszcze inny tekst"; if ( strText. [3] == 'j' )
AfxMessageBox("No pewnie, ten znak to 'j'."); else
AfxMessageBox( "Pewnie wystąpił jakiś błąd!" );
Załóżmy, że wywołujesz funkcję oczekującą wskaźnika do znaku, lecz pracujesz z obiektem cstring. Być może instynktownie spodziewasz się że musisz użyć funkcji GetBufferO w celu otrzymania wskaźnika do danych obiektu cstring, aby móc przekazać go funkcji. Jeśli argument funkcji został zadeklarowany jako const char *, wtedy wystarczy użycie operatora LPCTSTR klasy cstring. Wszystko co musisz zrobić to przekazać obiekt cstring funkcji, a szczegółami zajmie się operator. Poniższy przykład przedstawia funkcję oczekującą jako argumentu const char* oraz przykład jej poprawnego wywołania:
void ShowText( const char *lpszText ) {
AfxMessageBox( lpszText ) ;
}
void UseShowText( void } {
CString strText = "To jest kolejny tekst";

ShowText( strText);
}
Operując zawartością obiektów cstring z pewnością docenisz dwa kolejne operatory, a mianowicie operator + i operator +=. Ich użycie ilustrują dwa następne przykłady:
Przykład 1. Użycie operatora +:
CString strOne = "To jest ";
CString strTwo = "test";
CString strThree = strOne + strTwo;
// Teraz strThree zawiera łańcuch "To jest test"
Przykład 2. Użycie operatora +=:
CString strOne = "To jest ";
strOne += "test";
// Teraz strOne zawiera łańcuch "To jest test"
Porównania łańcuchów
Porównywanie dwóch obiektów cstring jest bardzo łatwe. Sprawia to sześć operatorów, < <=, >, >=, == oraz ! =. Każdy z tych operatorów zwraca wartość TRUE lub FALSE w zależności od tego, czy dana relacja jest prawdziwa czy fałszywa. Trzy następne przykłady ilustruj ą użycie tych operatorów:
Przykład 1. Użycie operatora = = klasy cstring:
CString strOne = "To jest jeden";
CString strTwo = "To jest dwa";
BOOL bResult;
bResult = ( strOne == strTwo );
// Teraz bResult zawiera wartość FALSE
Przykład 2. Użycie operatora ! = klasy cstring:
CString strOne = "To jest jeden";
CString strTwo = "To jest dwa";
BOOL bResult;
bResult = ( strOne != strTwo );
// Teraz bResult zawiera wartość TRUE
Przykład 3. Użycie operatora >= klasy cstring:
CString strOne = "To jest jeden";
CString strTwo = "To jest dwa";
BOOL bResult;
bResult = ( strOne >= strTwo );
// Teraz bResult zawiera wartość TRUE
Czasem program potrzebuje więcej niż jedynie wartość logiczną. Przykładem mogą być funkcje sortujące. Aby móc posortować łańcuchy, potrzebujesz wartości mniejszej od zera, gdy pierwszy łańcuch jest mniejszy od drugiego, wartości równej zeru, gdy oba łańcuchy są sobie równe i wartości większej od zera gdy łańcuch pierwszy jest większy od drugiego. W tym celu możesz skorzystać z funkcji składowej Compare (). Ta funkcja wykonuje porównanie dwóch łańcuchów (biorąc pod uwagę wielkości liter). Oto trzy przykłady:
Przykład 1. Porównywanie łańcucha większego od łańcucha w obiekcie cstring:
CString strText( "abc" );
int nResult = strText.Compare( "abd" );
// Teraz nResult ma wartość mniejszą od zera
Przykład 2. Porównywanie łańcucha mniejszego od łańcucha w obiekcie cstring:
CString strText( "abc" );
int nResult = strText.Compare( "abb" );
// Teraz nResult ma wartość większą od zera
Przykład 3. Porównywanie łańcucha cstring identycznego z łańcuchem w innym obiekcie cstring:
CString strOne( "abc" );
CString strTwo( "abc" );
int nResult = strOne.Compare( strTwo );
// Teraz nResult ma wartość równą zeru
Jeśli chcesz przeprowadzić porównanie niezależne od wielkości liter, powinieneś użyć funkcji CompareNoCase (). Działa ona tak samo jak funkcja Compare (), z tym że zwracane wyniki nie zależą od wielkości liter w łańcuchach.
Wydzielanie podłańcuchów
Często występuje potrzeba wydzielenia podłańcucha z łańcucha znaków. Na przykład, w obiekcie cstring jest zawarty długi napis, lecz potrzebujesz jedynie pierwszych dziesięciu znaków. Dzięki trzem funkcjom, Left (), Mid () oraz Right () wydzielanie podłańcuchów jest bardzo łatwe.
Funkcja Left () wydziela obiekt cstring zawierający kopię podanego zakresu znaków. Poniższy przykład pokazuje, w jaki sposób wydzielić podłańcuch zawierający pierwsze dziesięć znaków innego łańcucha:
CString strText = "To jest testowy napis";
CString strPart = strText.Left(10);
// Teraz strPart zawiera łańcuch "To jest te"
Funkcja Right () wydziela obiekt cstring zawierający kopię podanego zakresu znaków. Różnica pomiędzy funkcjami Right () i Left () polega na tym, że funkcja Right () zwraca znaki położone na końcu łańcucha (po prawej stronie). Poniższy przykład pokazuje, w jaki sposób wydzielić podłańcuch zawierający ostatnie dziesięć znaków innego łańcucha:
CString strText = "To jest testowy napis";
CString strPart = strText.Right(10);
// Teraz strPart zawiera łańcuch "towy napis"
Funkcja Mid (), jak zapewne się domyślasz, wydziela podłańcuch ze środka innego łańcucha. Ta funkcja występuje w dwóch wersjach. Pierwsza z nich wymaga podania pojedynczego argumentu określającego indeks pierwszego znaku wydzielanego podłańcucha. Ta wersja wydziela podłańcuch od wskazanego znaku do końca łańcucha. Druga wersja funkcji wymaga podania dwóch argumentów. Pierwszy z nich określa indeks pierwszego znaku wydzielanego podłańcucha, zaś drugi argument określa ilość znaków w podłańcuchu. Sposób wykorzystania obu wersji funkcji Mid () znajdziesz w poniższych przykładach:
Przykład 1. Funkcja Mid () z jednym argumentem:
CString strText = "To jest testowy napis";
CString strPart = strText.Mid(10);
// Teraz strPart zawiera łańcuch "stowy napis"
Przykład 2. Funkcja Mid () z dwoma argumentami:
CString strText = "To jest testowy napis"; CString strPart = strText.Mid(10, 4); // Teraz strPart zawiera łańcuch "stow"
Wyszukiwanie podłańcuchów
Kiedyś z pewnością będziesz zmuszony do wyszukiwania podłańcucha zawartego w innym łańcuchu. Służą do tego trzy funkcje składowe: Find (), ReverseFind () oraz FindOneOf (). Funkcja Find () wyszukuje wystąpienia łańcucha wewnątrz obiektu cstring. Jeśli dany łańcuch zostanie znaleziony, funkcja zwraca indeks znaku, od którego zaczyna się szukany
łańcuch. Jeśli w obiekcie cstring szukany łańcuch nie zostanie znaleziony, funkcja zwraca wartość -l. Na przykład, jeśli obiekt cstring zawiera tekst Nazywam się Thomas Jef f erson i poszukasz łańcucha się, funkcja Find () zwróci wartość 8. Jeśli w tym samym łańcuchu poszukasz łańcucha kotek, funkcja zwróci wartość -l. Oto przykład użycia funkcji Find ():
CString strText = "Nazywam się Thomas Jefferson"; int nlndex = strText.Find( "się" ); // Teraz nlndex ma wartość 8 nlndex = strText.Find( "kotek" ); // Teraz nlndex ma wartość -l
Funkcja ReverseFind (} działa tak samo jak funkcja Find (), z tym że znaki wyszukiwane są od końca (od prawej strony) łańcucha. Poniższy przykład przedstawia różnicę pomiędzy funkcją Find () a ReverseFind () :
CString strText = "Mam na imię Thomas. A jak ty masz na imię?";
int nlndex = strText.Find( "imię" );
// Teraz nlndex ma wartość 7
nlndex = strText.ReverseFind( "imię" );
// Teraz nlndex ma wartość 37
Funkcja FindOneOf o jest używana do wyszukiwania w łańcuchu pierwszego znaku występującego w łańcuchu przekazanym jako argument funkcji. Na przykład, jeśli obiekt cstring zawiera napis "To be or not to be, that is the question" i jako argument funkcji FindOneOf () przekażesz łańcuch "qz", zostanie zwrócona wartość 32. Stanie się tak, ponieważ pierwszym znakiem z łańcucha "qz" występującym w przeszukiwanym łańcuchu jest litera "q" na pozycji 32.
Program StringDemo
Poniższy demonstracyjny program o nazwie StringDemo (listing 15.1) ilustruje sposób użycia podstawowych funkcji klasy cstring. Po uruchomieniu aplikacja ładuje plik tekstowy o nazwie AESOP.txt, zawierający pewną ilość pouczających greckich przypowieści.
Jak pokazano na rysunku 15.1, możesz wyszukiwać w tym pliku dowolnego ciągu znaków. Możesz zdecydować się na wyszukiwanie z uwzględnianiem lub bez uwzględniania wielkości liter. Możesz także wyszukiwać ciągi znaków i zastępować je innymi. Gdy zostanie przeprowadzona operacja wyszukiwania i zastępowania, zmodyfikowany tekst możesz przejrzeć w programie WordPad. (Podczas przeglądania tekstu w tym programie pamiętaj, by włączyć zawijanie wierszy).
StringDemo
Położenie na płytce CD-ROM: Rozdzl5\StringDemo
Nazwa programu: StringDemo.exe
Moduły kodu źródłowego w tekście: StringDemoView.cpp
Listing 15.1. StringDemo
// StringDemoView. cpp : implementation of // the CStringDemoView class
//
////////////////////////////////////////////////////////////, // CStringDemoView construction/destruction

CStringDemoView : : CStringDemoView () )
// Próba otwarcia pliku
CFile cf;
if( cf.0pen( "AESOP.txt", CFile : :modeRead ) ){
// Pobranie długości pliku.
int nFileLength = cf .GetLength ( ) ;

// Alokacja tymczasowego bufora.
char *lpBuffer = new char [nFileLength+1] ;
try{
// Odczyt danych.
cf.Read( IpBuffer, nFileLength
}
// Wychwycenie ewentualnego błędu odczytu. catch( CFileException *e ){
e->Delete ( } ;
delete [] IpBuffer;
return;
}
// Zakończenie łańcucha znakiem NULL. IpBuffer[nFileLength] = 0;

// Ustawienie obiektów CString m_strOriginalText // i m_strAlteredText. m_strOriginalText = IpBuffer; m_strAlteredText = m_strOriginalText;
// Zwolnienie tymczasowego bufora, delete [] IpBuffer;
}
}
CStringDemoView::~CStringDemoView()
}
//////////////////////////////////
//CStnngDemoView drawing
{
void CStnngDemoView: :OnDraw(CDC* pDC) {
CStringDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

// Poiformowanie użytkownika o załadowaniu pliku. if( m_strOriginalText.GetLength() > O )
pDC->TextOut( 10, 10, "Plik AESOP.txt jest załadowany." ); else
pDC->TextOut( 10, 10, "Plik AESOP.txt nie jest załadowany."
};
//////////////////////////////////////////////
// CStringDemoView mesage handlers
void CStringDemoView::OnFilePerformwordcount() {
CGetWord GetWord;

// Pobranie słowa w oknie dialogowym. if( GetWord.DoModal() == IDOK ){

// Zerowanie licznika.
int nCount = 0;
int nlndex;
CString strTempstring;

// Ustawienie tymczasowego obiektu CString // w celu przechowania zmienionego tekstu. strTempstring = m_strAlteredText;

// Ustawienie tymczasowego łańcucha zawierającego // wyszukiwane słowo. CString strTempWord =
GetWord.m_strWordToCount;

// Jeśli nie liczy się wielkość liter,
// oba łańcuchy zamieniamy na wielkie litery.
if( !GetWord.m_bCaseSensitive ){
strTempWord.MakeUpper();
strTempstring.MakeUpper(};
}
do{
// Próba znalezienia słowa. nlndex =
strTempstring.Find( strTempWord
// Jeśli nlndex != -l, oznacza to, że znaleźliśmy słowo, if( nlndex != -l }{

// Sprawdź, czy wcześniejszy znak nie jest
// alfanumeryczny.
// Jeśli tak, oznacza to, że znalezione słowo
// stanowi część innego słowa.
if ( nlndex ===== O l l
!isalnum( strTempString.GetAt( nlndex - l ) )){

// Sprawdź, czy następny znak nie jest
// alfanumeryczny.
// Jeśli tak, oznacza to, że znalezione słowo
// stanowi część innego słowa.

if( !isalnum( strTempString.GetAt(
nlndex + strTempWord.GetLength() ) ) )

// Zwiększenie licznika. nCount++;
}
// Skrócenie łańcucha zawierającego // ciało danych. strTempString =
strTempString.Right(
strTempString.GetLength() -strTempWord.GetLength() -nlndex ); } } while( nlndex != -l );

// Sformatowanie napisu i poinformowanie
// użytkownika o ilości znalezionych słów.
CString strlnfo;
strlnfo.Format( "Znalezionych słów: %d.", nCount);
AfxMessageBox( strlnfo );
}
}
void CStringDemoYiew::OnFileRestoreoriginalfiledata
{
// Zastąpienie tekstu roboczego oryginalnym tekstem. m strAlteredText = m strOriginalText;
}
void CStringDemoView::OnFileSearchandreplace() {
CSearchReplace SearchReplace;
// Pobranie słowa do znalezienia i zastąpienia. if( SearchReplace. DoModal () === IDOK ){
// Sprawdzenie, czy łańcuchy są identyczne.
if ( SearchReplace .m_strWordToFind ==
SearchReplace .m_strWordToReplace ) {
Af xMessageBox ( "Te dwa łańcuchy są identyczne!");
return;

// Zerowanie licznika.
int nCount = 0;
int nlndex;
CString strTempString;

// Ustawienie tymczasowego obiektu CString // w celu przechowania zmienionego tekstu. strTempString = m_strAlteredText ;

// Ustawienie tymczasowego łańcucha zawierającego // wyszukiwane słowo. CString strTempWord =
SearchReplace .m_strWordToFind ;

// Jeśli nie liczy się wielkość liter,
// oba łańcuchy zamieniamy na wielkie litery.
if( ! SearchReplace .m_bCaseSensitive ){
strTempWord. MakeUpper () ;
strTempString . MakeUpper () ;
}
do{
BOOL bReplaced = FALSE;

// Próba znalezienia słowa. nIndex =
strTempString.Find( strTempWord );
// Jeśli nIndex != -l, oznacza to, że znaleźliśmy słowo. if ( nlndex != -l ) {

// Sprawdź, czy wcześniejszy znak nie jest
// alfanumeryczny.
// Jeśli tak, oznacza to, że znalezione słowo
// stanowi część innego słowa.
if( nlndex == O l l
!isalnum( strTempString.GetAt( nlndex - l ) )){

// Sprawdź, czy następny znak nie jest
// alfanumeryczny.
// Jeśli tak, oznacza to, że znalezione słowo
// stanowi część innego słowa.
if ( !isalnum( strTempString.GetAt(
nlndex + strTempWord.GetLength() ) ) }{

// Zwiększenie licznika. nCount++;
// Obliczenie ilości znaków
// na prawo od znalezionego słowa.
int nRightLength =
strTempstring.GetLength() -
nlndex -
strTempWord.GetLength(); // Obliczenie ilości znaków // na lewo od znalezionego słowa. int nLeftLength =
m_strAlteredText.GetLength() -
nRightLength -
StrTempWord.GetLength();

// Formowanie lewego i prawego łańcucha. CString strLeft, strRight; strLeft =
m_strAlteredText.Left( nLeftLength ); strRight =
m_strAlteredText.Right( nRightLength );

// Złożenie w całość zmienionego łańcucha. m_strAlteredText = strLeft +
SearchReplace.m_strWordToRepiace +
strRight;
}
}
// Skrócenie tymczasowego łańcucha.
strTempString =
strTempString.Right(
strTempString.GetLength() -strTempWord.GetLength(} -nIndex );
}
} while( nlndex != -l );

// Sformatowanie napisu i poinformowanie // użytkownika o ilości zastąpionych słów. CString strlnfo;
strlnfo.Format( "Dokonanych zastąpień: %d.", nCount); AfxMessageBox( strInfo );
}
}
void CStringDemoYiew::OnFileYiewalterationsinnotepad() {
CFile Out;
// Próba stworzenia tymczasowego pliku, który zostanie
// załadowany do WordPada.
if( Out.0pen( "StringDemoTempFile.txt",
CFile::modeCreate l CFile::modeWrite ) ){
try{
// Zapis danych.
Out.Write( m_strAlteredText,
m_strAlteredText .GetLength () );
}
// Wychwycenie ewentualnego błędu zapisu catch( CFileException *e ){
e->Delete ( } ;
return;
}
// Zamknięcie pliku po tp, by WordPad // nie zgłosił błędu dostępu. Out.Close ();

// Uruchomienie WordPada funkcją WinExec() WinExec( "WRITE.EXE StringDemoTempFile.txt", SW NORMAL );
}
}
Klasa CFile
Ładowanie informacji z pliku i zapisywanie informacji do pliku odnosi się do prawie każdej stworzonej aplikacji. Zapisywanymi danymi może być wszystko, od obrazków, poprzez tekst aż po wyspecjalizowane dane binarne. W sercu obsługi plików w MFC leży klasa CFile. W tej sekcji nauczymy się obsługiwać pliki za pomocą jej funkcji składowych.
Pierwszą rzeczą, którą trzeba zrobić, jest utworzenie obiektu CFile. W większości przypadków użyjesz konstruktora nie posiadającego argumentów, jednak mogą przydać Ci się także jego dwie dodatkowe wersje. Pierwsza z nich otrzymuje pojedynczy argument - uchwyt HFILE już otwartego pliku. Druga wersja otrzymuje dwa argumenty -nazwę pliku oraz wartość całkowitą określającą sposób otwarcia pliku. Druga wersja konstruktora odpowiada użyciu konstruktora bezargumentowego, a następnie wywołanie funkcji składowej open ().
W celu otwarcia pliku, po skonstruowaniu obiektu CFile należy wywołać funkcję open (). Oczywiście, nie trzeba tego robić, jeśli został użyty konstruktor otwierający plik lub pobierający uchwyt już otwartego pliku. Funkcja Open() wymaga podania dwóch argumentów - nazwy pliku oraz wartości całkowitej określającej sposób jego otwarcia. Zwracaną wartością jest wartość typu BOOL wskazująca sukces lub porażkę. Funkcja Open (), w odróżnieniu od większości funkcji składowych klasy CFile, nie zgłasza wyjątku w przypadku niepowodzenia.
Znacznikami określającymi sposób otwarcia pliku może być dowolna kombinacja wartości z tabeli 15.1. Do łączenia tych wartości możesz użyć bitowego operatora l (lub). Wymagane jest użycie jednego znacznika dostępu (modę) i jednego znacznika wspólnego korzystania (share); tryby modeCreate i modeNoinherit są opcjonalne.
Tabela 15.1. Znaczniki otwierania pliku
Znacznik
Opis
CFile::modeCreate
Nakazuje konstruktorowi utworzenie nowego pliku.Jeśli plik już istnieje, jego długość jest obcinana do zera.


CFile::modeNoTruncate
Połącz tę wartość zmodeCreate. Jeśli tworzony plik już istnieje, nie jest obcinany do zera. W ten sposób jest zagwarantowane otwarcie pliku, albo jako nowego pliku, albo jako pliku już istniejącego. Może to być przydatne na przykład przy otwieraniu pliku konfiguracyjnego, który nie musi przedtem istnieć. Ta opcja odnosi się także do klasy CStdioFile.

CFile::modeRead
Otwiera plik tylko do odczytu.
CFile::modeReadWrite
Otwiera plik do odczytu i zapisu.
CFile::modeWrite
Otwiera plik tylko do zapisu.
CFile::modeNo!nherit
Zapobiega dziedziczeniu pliku przez procesy potomne.
CFile::shareDenyNone
Otwiera plik bez zabraniania innym procesom odczytu lub zapisu do pliku. Otwarcie się nie powiedzie, jeśli plik został wcześniej otwarty (w trybie zgodności) przez inny proces.

CFile::shareDenyRead
Otwiera plik i blokuje odczyt z pliku przez inne procesy. Otwarcie się nie powiedzie, jeśli plik został wcześniej otwarty (w trybie zgodności lub w trybie tylko do odczytu) przez inny proces.
CFile::shareDenyWrite
Otwiera plik i blokuje zapis do pliku przez inne procesy. Otwarcie się nie powiedzie, jeśli plik został wcześniej otwarty (w trybie zgodności lub w trybie tylko do zapisu) przez inny proces.
CFile::shareExclusive
Otwiera plik w trybie wyłączności, blokując do niego dostęp innym procesom. Otwarcie się nie powiedzie, jeśli plik został wcześniej otwarty w jakimkolwiek innym trybie do zapisu lub odczytu, nawet przez bieżący proces.

CFile::shareCompat
Ten znacznik nie jest dostępny w 32-bitowym MFC. Odpowiada użyciu znacznika shareExclusive w funkcji Cf ile: :Open ().
CFile::shareText
Ustawia tryb tekstowy ze specjalnym traktowaniem par powrót karetki-nowa linia (używane wyłącznie w klasach pochodnych).
CFile::shareBinary
Ustawia tryb binarny (używane wyłącznie w klasach pochodnych).

Trzy poniższe przykłady przedstawiaj ą sposób otwarcia pliku do odczytu:
Przykład 1. Użycie konstruktora CFile bez żadnych argumentów:
CFile cf;
if( cf.0pen( "Nazwapliku", CFile::modeRead ))
AfxMessageBox ( "Powiodło się otwarcie pliku" ); else
AfxMessageBox( "Otwarcie pliku się nie powiodło" );
Przykład 2. Użycie konstruktora CFile z uchwytem pliku:
HFILE hFile = _lopen ( "Nazwapliku" , OF_READ ) if( hFile != HFILE_ERROR ){
// Plik został poprawnie otwarty
CFile cf { hFile ) ;
}
Przykład 3. Użycie konstruktora CFile z podaniem nazwy pliku i znaczników trybu otwierania:
try {
CFile cf ( "Nazwapliku", CFile : :modeRead ) ;
// W tym miejscu wykonanie operacji na pliku } catch( CFileException *e ){
// Poinformowanie użytkownika o błędzie
CString strError;
strError . Format ( "Błąd nr %d", e->m_cause );
e->Delete ( ) ;
AfxMessageBox ( strError ) ;
}
Gdy otworzysz plik do odczytu, oznacza to, że masz zamiar odczytywać z niego dane. W tym celu użyj funkcji Read ( ) . Wymaga ona podania dwóch argumentów. Pierwszym z nich jest wskaźnik do bufora, w którym mają zostać umieszczone odczytane dane, zaś drugi to ilość bajtów do odczytania. Funkcja zwraca ilość rzeczywiście odczytanych bajtów. Oto przykład odczytywania danych z pliku:
CFile cf;
if( cf.0pen( "Nazwapliku", CFile : :modeRead )){ char cbBuffer [1000] ; try{
// Odczytanie danych do bufora
int nBytesRead = cf.Readf cbBuffer, sizeof( cbBuffer ) ) ; } catch( CFileException *e ){
// Powiadom użytkownika o błędzie
e->Delete ( ) ;
}
}
Jeśli otworzysz plik do zapisu (używając samego znacznika CFile: :modeWrite lub w połączeniu z innymi znacznikami), będziesz mógł zapisywać dane do pliku. Funkcja zapisująca dane, write (), podobnie jak funkcja Read () wymaga podania dwóch argumentów. Pierwszym z nich jest wskaźnik do bufora, w którym znajdują się dane przeznaczone do zapisania do pliku, zaś drugi to ilość bajtów do zapisania. Funkcja nie zwraca żadnej wartości. Brak wartości określającej ilość rzeczywiście zapisanych danych może być czasem dokuczliwy. Oto przykład zapisywania danych do pliku:
CFile cf;
if( cf.0pen( "Nazwapliku", CFile:rmodeWrite )){
char szData[] = "To jest test";
try{
// Przejście na koniec pliku, tak,aby nie zniszczyć
// już istniejących w nim danych
cf.SeekToEnd();
cf.Write( szData, strlen( szData )); } catch( CFileException *e ){
// Powiadom użytkownika o błędzie
e->Delete();
}
}
W poprzednim przykładzie możesz zauważyć, że za pomocą funkcji seekToEndf) przenieśliśmy się na koniec pliku. Uczyniliśmy to w tym celu, aby dane już zawarte w pliku nie zostały zastąpione przez zapisywane dane. Inne przydatne funkcje nawigacyjne to Seek() oraz SeekToBegin(). Funkcja Seek() wymaga dwóch argumentów. Pierwszy to ilość bajtów, o jakie należy się w pliku przesunąć, zaś drugi to wartość określająca sposób tego przesunięcia. Opcje dostępne dla funkcji Seek () zostały zebrane w tabeli 15.2.
Tabela 15.2. Tryby przesuwania wskaźnika pliku funkcją Seek()
Tryb
Opis
CFile : : begin
Przesuwa wskaźnik pliku o określoną ilość bajtów względem początku pliku.
CFile : : current
Przesuwa wskaźnik pliku o określoną ilość bajtów względem bieżącej pozycji w pliku.
CFile: :end
Przesuwa wskaźnik pliku o określoną ilość bajtów względem końca pliku. Zwróć uwagę, że aby znaleźć się w pliku, podana ilość bajtów musi być ujemna. Wartość dodatnia spowoduje przesunięcie poza koniec pliku.

Jedną z funkcji związanych z operacjami na plikach, jakiej najczęściej potrzebowałem w swoich programach, była funkcja kopiująca zawartość pliku. Napisałem taką funkcję i często używałem jej w swoich programach. Funkcja nosi nazwę copyFile () i wymaga podania trzech argumentów. Pierwszy z nich to nazwa pliku źródłowego, drugi to nazwa pliku docelowego, zaś trzeci to rozmiar bufora, jaki ma zostać użyty podczas operacji kopiowania. Jeśli jednak podany bufor będzie zbyt duży, operacja może się nie powieść, tak więc zalecam używanie bufora o rozmiarze mniej więcej SOK. Funkcja zwraca wartość logiczną określającą, czy operacja zakończyła się powodzeniem.
Funkcja CopyFile () wychwytuje wszystkie wyjątki plików i zgłasza komunikat błędu. Kod źródłowy funkcji znajduje się na płytce CD-ROM, w pliku CopyFile.cpp w folderze Rozdzl5\CopyFile. Plik stanowi również część programu FileDemo opisywanego w następnej sekcji.
Program FileDemo
Klasy CFile często będziesz używał do ładowania i zapisywania buforów danych. Z tego powodu napisaliśmy program FileDemo, który zajmuje się właśnie tym. Umożliwia on stworzenie listy plików, a następnie zapisuje je wszystkie w jednym dużym pliku. Możesz także skopiować pojedynczy plik do innego pliku. W tym celu wybierz po prostu polecenie Skopiuj pierwszy plik. Program skopiuje wtedy pierwszy plik z listy do pliku o wskazanej nazwie. Fragmenty kodu źródłowego znajdują się na listingu 15.2.
Oto kilka uwag odnoszących się do funkcji CopyFile (). Pierwsza z nich odnosi się do użycia funkcji GetstatusO i Setstatus (). Funkcja GetstatusO jest używana do pobrania stanu pliku. W tym przypadku szczególnie ważne są data i czas utworzenia pliku źródłowego. Te informacje są następnie wykorzystywane do ustawienia daty i czasu pliku docelowego, tak aby zgadzały się one z plikiem źródłowym.
Kolejna uwaga odnosi się do użycia funkcji GetLength () do pobrania rozmiaru pliku źródłowego. Dzięki temu wiemy, ile bajtów musimy skopiować. Kopiujemy po nBufferSize bajtów naraz, korzystając z lokalnego bufora. Zwiększając rozmiar bufora, możesz poprawić wydajność funkcji CopyFile ().
FileDemo
Położenie na płytce CD-ROM: Rozdzl5\FileDemo
Nazwa programu: FileDemo.exe
Moduły kodu źródłowego w tekście: FileDemo View.cpp
Listing 15.2. FileDemo___________________________________________
// FileDemoYiew.cpp : implementation of // the CFileDemoView class
//
//////////////////////////////////////////////////////////
//CFileDemoView drawing
void CFileDemoView::OnDraw(CDC* pDC) {
CFileDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

// Rysowanie listy plików.
int y = 0;
for( int i=0; i// Pobranie łańcucha o tym indeksie.
CString strText = m_Filename.GetAt( i );
// Narysowanie go w kontekście urządzenia
pDC->TextOut( O, y, strText );
// Pobranie rozmiaru w celu zwiększenia
// wsp. y.
CSize size =
pDC->GetTextExtent( strText, l );
// Zwiększenie wsp. y o wysokość tekstu, y += size.cy;
}
}
// CFileDemoYiew message handlers
char szFilter[] = "Wszystkie pliki(*.*)|*.*|";

void CFileDemoview::OnFileAddtolist()
{
// Standardowe okno dialogowe. CFileDialog FileDlg( TRUE, NULL, NULL, OFN_HIDEREADONLY, szFilter ) ;

if( FileDlg.DoModal() == IDOK ){
// Dodanie pełnej ścieżki i nazwy pliku
// do listy m_Pathname.
m_Pathname.Add( FileDlg.GetPathName() );
// Dodanie nazwy pliku do listy
// m_Filename.
m_Filename.Add( FileDlg.GetFileName() );

// Wymuszenie odświeżenia okna. InvalidateRect( NULL, TRUE ); UpdateWindow();
}
}
void CFileDemoYiew: : OnFileSavef ilesinlist()
{
// Sprawdzenie, czy mamy na liście
// jakieś pliki.
if( m_Pathname.GetSize () == O ) {
Af xMessageBox ( "Na liście nie ma żadnych plików
return;
}
// Standardowe okno dialogowe. CFileDialog FileDlg( FALSE, NULL, NULL, OFN_HIDEREADONLY, szFilter ) ;
if( FileDlg.DoModal() == IDOK ){
// Próba otwarcia pliku wyjściowego.
CFile Out;
if( Out.0pen( FileDlg.GetPathName(),
CFile::modeWrite | CFile::modeCreate } ){
// Przejście przez wszystkie pliki.
for( int i=0; i
// Próba otwarcia pliku wejściowego. CFile In;
if ( In.0pen( m_Pathname . GetAt ( i ), CFile: :modeRead ) ){
// Tymczasowy bufor char cbBuffer[4000] ;
// Pobranie rozmiaru pliku wejściowego, abyśmy // wiedzieli, kiedy zakończyć zapisywanie.
int nFilesize = In .GetLength ( ) ; while( nFilesize > O ){
// Zaczynamy od założenia, że odczytamy // tyle bajtów, aby zapełnić bufor. int nSize = sizeof ( cbBuffer ) ;
// Jeśli ilość bajtów pozostałych do // odczytu nie jest wystarczająca do // zapełnienia bufora, dostosowujemy rozmiar, if ( nSize > nFilesize ) nSize = nFilesize;
// Odczytanie bajtów i wychwycenie // wszystkich ewentualnych wyjątków. try{
In.Read( cbBuffer, nSize } ;
} catch( CFileException *e ){
// Sformatowanie komunikatu
// od systemu/
char *lpMsgBuf;
if ( FormatMessage (
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, e->m_10sError, MAKELANGID( LANG_NEUTRAL,
SUBLANG_DEFAULT ) ,
(LPSTR) SlpMsgBuf , O, NULL ) > O ) { AfxMessageBox ( IpMsgBuf ) ; LocalFree( IpMsgBuf ) ; }
// Zwolnienie wyjątku i powrót.
e->Delete ( ) ;
return;
}
// Zapis danych i wychwycenie
// wszystkich ewentualnych wyjątków
try{
Out.Write( cbBuffer, nSize );
} catch( CFileException *e ){
// Sformatowanie komunikatu od systemu char *lpMsgBuf; if ( FormatMessage (
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, e->m_10sError,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ) ,
(LPSTR) SlpMsgBuf , O, NULL ) > O ){
AfxMessageBox ( IpMsgBuf ) ;
LocalFreet IpMsgBuf ) ;
}
// Zwolnienie wyjątku i powrót. e->Delete ( ) ; return; } nFilesize -= nSize;
}
}
// Alert user to problem, else
AfxMessageBox( "Could not open " + m Pathname.GetAt( i ) );
}
}
// Poinformowanie użytkownika o problemie, else
AfxMessageBox(
"Nie powiodło się utworzenie pliku wyjściowego.");
}
}
void CFileDemoYiew: : EmptyStringArrays ( void)
{
// Opróżnienie tablicy zawierającej // ścieżki dostępu i nazwy pliku // oraz tablicy zawierającej tylko // nazwę pliku. m_Pathname .RemoveAll () ; m Filename . RemoveAll ( ) ;
}
void CFileDemoYiew: : OnFileEmptylist ( )
{
// Opróżnienie wszystkiego // i wymuszenie odświeżenia okna. EmptyStringArrays ( ) ; InvalidateRect ( NULL, TRUE ); UpdateWindow ( ) ;
}
BOOL CFileDemoView::CopyFile( const char *lpSrcFilename,
const char *lpDestFilename, int nBuffersize ) {
CFile Out, In;
int nFilesize;
char *lpBuffer;
// Próba otwarcia pliku wejściowego
if( !In.Open( IpSrcFilename, CFile::modeRead ) }{
AfxMessageBox(
"Nie powiodło się otwarcie pliku wejściowego.
return ( FALSE );
}
// Próba stworzenia pliku wyjściowego if( !Out.Open( IpDestFilename,
CFile::modeWrite | CFile::modeCreate ) ){
AfxMessageBox(
"Nie powiodło się stworzenie pliku wyjściowego
return( FALSE );
}
// Tworzenie bufora dla kopiowania. IpBuffer = new char [nBuffersize]; if( IpBuffer == NULL ){
AfxMessageBox(
"Nie powiodła się alokacja bufora kopiowania."
return ( FALSE ) ;
}
// Pobranie stanu pliku wejściowego dzięki czemu będziemy
// mogli ustawić taki sam stan pliku wyjściowego.
// W ten sposób zachowamy elementy, takie
// jak data i czas utworzenia pliku.
CFileStatus rStatus;
In.GetStatus( IpSrcFilename, rStatus );

// Pobranie rozmiaru pliku, abyśmy wiedzieli // kiedy zakończyć kopiowanie. nFilesize = In.GetLength();

while( nFilesize > O ){

// Zaczynamy od założenia, że odczytamy // tyle bajtów, aby zapełnić bufor, int nSize = nBuffersize;
// Jeśli ilość bajtów pozostałych do // odczytu nie jest wystarczająca do // zapełnienia bufora, dostosowujemy rozmiar, if ( nSize > nFilesize } nSize = nFilesize;
// Odczytanie bajtów i wychwycenie // wszystkich ewentualnych wyjątków. try{
In.Read( IpBuffer, nSize );
} catch( CFileException *e ){
// Sformatowanie komunikatu
// od systemu.
char *lpMsgBuf;
if( FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MES SAGE_FROM_SYSTEM, NULL, e->m_10sError, MAKELANGID( LANG_NEUTRAL,
SUBLANG_DEFAULT ), (LPSTR) SlpMsgBuf, O, NULL ) > O AfxMessageBox( IpMsgBuf ); LocalFree( IpMsgBuf ); }
// Zwolnienie wyjątku i powrót. e->Delete(); return( FALSE );
}
// Zapis danych i wychwycenie
// wszystkich ewentualnych wyjątków
try{
Out.Write( IpBuffer, nSize };
}
catcht CFileException *e ){
// Sformatowanie komunikatu od systemu
char *lpMsgBuf;
if ( FormatMessage (
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, e->m_10sError, MAKELANGI D ( LANG_NEUTRAL ,
SUBLANG_DEFAULT ) ,
(LPSTR) &lpMsgBuf , O, NULL ) > O ) { AfxMessageBox ( IpMsgBuf ) ; LocalFree ( IpMsgBuf ) ;
}
// Zwolnienie wyjątku i powrót. e->Delete ( } ; return ( FALSE ) ;
}
nFilesize -= nSize;
}
// Zamknięcie pliku wyjściowego, aby
// powiodło się wywołanie funkcji SetStatus
Out .Close ( ) ;
CFile: :SetStatus ( IpDestFilename, rStatus };
// Usunięcie bufora. delete [] IpBuffer;
return ( TRUE ) ;
}
void CFileDemoYiew::OnFileCopyfirstfile()
{
// Sprawdzenie, czy na liście znajdują się
// jakieś pliki.
if( m_Pathname.GetSize() == O ){
AfxMessageBox ( "Na liście nie ma żadnych plików." };
return;
}
// Standardowe okno dialogowe. CFileDialog FileDlgt FALSE, NULL,NULL,
OFN_HIDEREADONLY, szFilter );

// Jeśli kliknięto przycisk OK, // przeprowadzamy kopiowanie. if( FileDlg.DoModal(} == IDOK ) CopyFile( m_Pathname.GetAt( O), FileDlg.GetPathName() );
}
Klasa CCompressedFile
Obecne komputery mają dyski przeciętnie od stu do dwustu razy większe niż dyski używane dziesięć lat temu. To dobra wiadomość dla użytkowników, którzy mogą przechować na dysku więcej aplikacji. Z drugiej strony, twórcy aplikacji prześcigają się w tworzeniu programów pożerających jak najwięcej miejsca. Tak więc, dla większości użytkowników ogólny zysk jest niewielki.
Oprócz tego, twórcy oprogramowania przestali kompresować dane. Doskonale zdają sobie sprawę, że dzisiejsze dyski są duże, nie muszą więc wysilać się w celu zmniejszenia objętości danych. Najgorsze są pliki graficzne i dźwiękowe. Standardowym formatem graficznym w Windows jest plik BMP, który jest zupełnie nieskompresowany. Pełno-ekranowy plik BMP o rozmiarach 640 x480 pikseli i 256 kolorach zajmuje 300K. Domyślnym formatem dźwiękowym w Windows są pliki WAV, które także nie są skompresowane i powszechnie osiągają po kilkaset kilobajtów objętości.
Na początku lat osiemdziesiątych, kiedy większość aplikacji mieściła się na pojedynczej dyskietce, programiści czynili ogromne wysiłki w celu jak największego ograniczenia objętości danych. Nie pytano się, czy kompresować dane -jedyne zagadnienie stanowił wybór metody kompresji. Minął czas i obecne aplikacje stały się bardzo wymyślne. Jednak ogromna aplikacji z mnóstwem danych nie jest żadną wymówką dla zużywania większej ilości miejsca na dysku niż to jest rzeczywiście potrzebne. Programiści muszą zacząć ponownie kompresować dane i zaprzestać wykorzystywania faktu, że twarde dyski stają się coraz pojemniejsze. Zaprezentowana w tej książce biblioteka klas oferuje Ci narzędzia, dzięki którym możesz łatwo kompresować i dekompresować dane. To łatwe, więc nie powinieneś się wykręcać!
Klasa kompresji danych, ccompressedFile, może być dodana do dowolnego projektu Visual C++. Oprócz bycia częścią przykładowego programu CompressDemo, jest zapisana także w oddzielnej kartotece na dołączonej do książki płytce CD-ROM. Tą kartoteką jest Rozdzl5\CompressedFile.
Program CompressDemo
Kolejny prosty program demonstruje zastosowanie naszej biblioteki klas. Gdy go uruchomisz, ujrzysz jedynie puste okno, pokazane na rysunku 15.2.
Program znajduje się w kartotece Rozdzl5\CompressDemo na dołączonej do książki płytce CD-ROM. Korzystając z menedżera plików, uruchom program CompressDemo.exe, będący programem wielodokumentowym (MDI).
Pierwszą rzeczą, którą musisz zrobić, jest załadowanie pliku. Możesz załadować dowolny plik, ale najlepiej kompresują się pliki BMP i WAV. Aby załadować plik, w menu Plik wybierz polecenie Otwórz, a następnie wskaż plik za pomocą standardowego okna otwierania plików. Po załadowaniu pliku w oknie pojawią się pewne informacje na jego temat. Jeśli plik nie został skompresowany za pomocą naszej biblioteki kompresji, rodzajem kompresji będzie Brak kompresji. Rysunek 15.3 przedstawia okno z załadowanym plikiem.
Następnie w celu przetestowania programu wybierz w menu Kompresja rodzaj kompresji. Do wyboru masz algorytm Huffmana, LZSS, LZW oraz brak kompresji. Domyślnym wyborem jest LZW, który zwykle daje najlepsze rezultaty. Tak jak na rysunku 15.4, na razie pozostaw więc kompresję LZW.
Aby zapisać plik z użyciem wybranej metody kompresji, w menu Plik wybierz polecenie Zapisz Jako. Pojawi się standardowe okno dialogowe Windows. Wpisz nazwę pliku, po czym kliknij przycisk OK. W naszym przykładzie zapisaliśmy plik jako TEST.CMP z użyciem algorytmu LZW. Po zapisaniu pliku zmienia się tekst w oknie informujący o rozmiarze i rodzaju kompresji zapisanego pliku (rysunek 15.5).
Kompresja danych
Wyjaśnienie wszystkich trzech metod kompresji stosowanych w naszej bibliotece kompresji pomoże Ci w zrozumieniu dalszej części rozdziału. W tej sekcji zaprezentujemy pokrótce każdą z metod.
Metoda Huffmana
Algorytm Huffmana podchodzi do kompresji danych w sposób statystyczny. Metoda polega na znajomości prawdopodobieństwa wystąpienia każdego ze znaków w tekście. Na podstawie prawdopodobieństw tworzona jest tabela kodów, w której różne kody mają różne ilości bitów. Kody o większym prawdopodobieństwie wystąpienia mają mniejszą ilość bitów, zaś kody o mniejszym prawdopodobieństwie wystąpienia mają większą ilość bitów.
Algorytm kompresji Huffmana rozpoczyna działanie od przejrzenia listy znaków i stworzenia odpowiadającej im listy prawdopodobieństw (lub zliczeń wystąpień). Dzięki temu staje się znana częstotliwość wystąpień każdego ze znaków. Lista jest sortowana według częstotliwości wystąpień, tak że na początku listy występują znaki najbardziej prawdopodobne. Następnie tworzone jest drzewo binarne, na podstawie którego dostępne są kody binarne każdego ze znaków.
Poszczególne symbole w drzewie są ułożone jako ciąg węzłów mających być powiązanych drzewem binarnym. Każdy węzeł posiada wagę, którą jest po prostu częstotliwość, czyli prawdopodobieństwo wystąpienia symbolu.
Lokalizowane są dwa wolne węzły o najmniejszych wagach. Tworzony jest węzeł nadrzędny dla tych węzłów, zaś jego wagą staje się suma wag obu węzłów potomnych. Węzeł nadrzędny jest dodawany do listy wolnych węzłów, zaś usuwane są z niej oba węzły potomne. Jeden z nich jest oznaczany jako ścieżka od węzła nadrzędnego dla de-kodowania bitu 0. Drugi z nich jest zaznaczany jako dekodujący bit 1. Opisane czynności są powtarzane aż do momentu, gdy pozostanie tylko jeden wolny węzeł. Ten wolny węzeł jest oznaczany jako korzeń drzewa.
LZSS
W metodzie LZSS przetworzony już tekst jest wykorzystywany jako słownik. Na jego podstawie, w celu uzyskania kompresji, frazy w tekście wejściowym są zastępowane wskaźnikami do zawartości słownika. Stopień kompresji zależy od długości fraz słownika, wielkości okna zawierającego przejrzany tekst oraz od entropii tekstu źródłowego z punktu widzenia modelu LZSS.
Główną strukturą danych algorytmu LZSS jest okno podzielone na dwie części. Pierwsza z nich składa się z dużego bloku ostatnio zdekodowanych danych. Druga, zwykle dużo mniejsza, stanowi bufor wejściowy. Bufor wejściowy zawiera znaki wczytane z wejścia, lecz jeszcze nie zdekodowane.
Zwykle rozmiar okna tekstu wynosi kilka tysięcy znaków. Bufor wejściowy jest zwykle dużo mniejszy, od 10 do 100 znaków. Algorytm próbuje dopasować zawartość bufora wejściowego do łańcucha w słowniku.
Kod implementujący algorytm kompresji jest dość prosty. Wyszukuje w oknie tekstu najdłuższy pasujący ciąg znaków, koduje go, a następnie przechodzi do dalszej części tekstu.
Kod dekompresji jest jeszcze prostszy, gdyż nie musi dokonywać porównań. Odczytuje kod, zapisuje do pliku odpowiadającą mu frazę, zapisuje następny znak, przesuwa się i powtarza to wszystko od początku. Korzysta z okna, lecz nie dokonuje porównań ciągów znaków.
LZW
LZW to schemat kompresji także oparty na słowniku. Frazy przechowywane są w potencjalnie nieograniczonym słowniku. Od algorytmu LZSS różni się tym, że odrzuca koncepcję okna tekstowego. LZW zapisuje serię kodów podobnie jak algorytm LZSS. Każdy kod LZW składa się z dwóch komponentów: położenia frazy oraz znaku występującego po tej frazie. Każdy kod zawiera wskaźnik określający frazę oraz pojedynczy znak następujący po tej frazie. Długość frazy jest znana dekoderowi, nie musi więc być częścią kodu.
Przy użyciu algorytmu LZW zarówno koder, jak i dekoder zaczynają od bardzo małego słownika zawierającego 256 fraz po jednym znaku każda. W miarę czytania znaków są one dodawane do bieżącego łańcucha. Dopóki bieżący ciąg pasuje do jakiejś frazy w słowniku, proces jest kontynuowany.
Jednak w pewnym momencie łańcuch nie będzie posiadał w słowniku pasującej frazy. Właśnie wtedy LZW zapisuje do pliku kod i znak. Pamiętajmy, że łańcuch posiadał odpowiednik w słowniku aż do ostatniego wczytanego znaku. Tak więc bieżący łańcuch jest zdefiniowany jako ostatnio pasująca fraza z dodanym jednym nowym znakiem.
Algorytm kompresji zapisuje więc do pliku indeks poprzedniej pasującej frazy oraz nowy znak. Następnie wykonywany jest dodatkowy krok. Nowa fraza, składająca się z pasującej frazy i nowego znaku, jest dodawana do słownika. Następnym razem gdy pojawi się nowa fraza, zostanie użyta do budowy jeszcze dłuższej frazy.
Algorytm LZW może ograniczyć rozmiar słownika fraz. W związku z tym powstają dwa zagadnienia, które trzeba rozważyć. Po pierwsze, musisz określić ilość bitów przeznaczonych na kod frazy. Po drugie (i bardziej ważne), musisz określić, jak wiele czasu procesora możesz przeznaczyć na przeszukiwanie słownika.
Funkcje klasy CCompressedFile
Ta sekcja stanowi opis publicznych funkcji składowych udostępnianych przez naszą klasę kompresji plików. Ponieważ przy tworzeniu biblioteki podstawowym celem było zachowanie prostoty, biblioteka nie zawiera wielu funkcji. Dzięki temu uzyskanie kompresji jest bardzo proste i wiąże się z dosłownie kilkoma liniami kodu.
Być może wolisz pominąć tą sekcję i przejść do następnej, zatytułowanej "Działanie programu CompressDemo", ilustrującej sposób użycia opisywanych tu funkcji. (Pewne osoby wolą poznać najpierw sposób użycia funkcji, a dopiero potem ewentualnie studiować ich składnię).
CcompressedFile::MakeNewFile() - ta statyczna funkcja tworzy obiekty klasy CCompressedFile. Pierwszym argumentem jest nazwa istniejącego pliku, który ma zostać otwarty. Drugi (opcjonalny) argument określa rodzaj kompresji, jaka ma zostać użyta. Jeśli w konstruktorze nie podasz argumentu rodzaju kompresji, zostanie domyślnie użyta kompresja LZW. To ważne: jeśli podasz nazwę pliku, tworzony obiekt przejmuje rodzaj kompresji zawarty w tym pliku, bez względu na rodzaj kompresji podany w drugim argumencie. Oto przykład utworzenia obiektu CCompressedFile dla kompresji LZW:
CCompressedFile *pFile = MakeNewFile( NULL, LZW );
CCompressedFile::Open() - Ta funkcja działa prawie tak samo jak funkcja CFile: : Open (). Otwiera plik do odczytu lub zapisu. Jednocześnie inicjuje wszystkie dane i zmienne potrzebne do kompresji lub dekompresji. Oto przykład otwarcia pliku do odczytu:
CCompressedFile *pFile = MakeNewFile( "Nazwapliku" );
if( pFile->Open("Nazwapliku", CFile::modeRead ))

AfxMessageBox("Otwarcie pliku się powiodło."); else
AfxMessageBox( "Otwarcie pliku się nie powiodło, delete pFile;
CcompressedFile::Close() - ta funkcja zrzuca zawartość wszystkich buforów i zamyka plik.
CcompressedFile::GetPosition() - ta funkcja zwraca pozycję wskaźnika pliku. Jeśli do nowo utworzonego pliku zostało zapisanych 500 bajtów, zwróconą wartością będzie 500. Jeśli z otwartego pliku zostało wczytanych 2048plików, zwróconą wartością będzie 2048. Oto przykład odczytu 500 bajtów i wyświetlenia w oknie położenia w pliku:
CCompressedFile *pFile = MakeNewFile( "Nazwapliku" ) ; if( pFile->Open( "Nazwapliku", CFile::modeRead ) ){
char cbBuffer[500];
pFile->Read( cbBuffer, 500 );
CString strText;
strText.Format( "Obecne położenie w pliku to: %ld", pFile->GetPosition() );
AfxMessageBox( strText );
}
else
AfxMessageBox("Błąd otwarcia pliku.");
delete pFile;
CcompressedFile::GetLength() - ta funkcja zwraca ilość bajtów w pliku.
CCompressedFile::FindFilelnArchive() - skompresowane archiwa plików tworzone z pomocą biblioteki klasy CCompressedFile mogą zawierać w jednym pliku kilka skompresowanych plików. Przed wywołaniem tej funkcji konieczne jest otwarcie skompresowanego pliku za pomocą funkcji Open (). Funkcja FindFile-inArchive() wyszukuje na liście plików plik oznaczony indeksem nindex. Jeśli w archiwum znajduje się pięć plików, zaś funkcji zostanie przekazany argument 2, zostanie wybrany plik o indeksie 2. Będzie to trzeci plik w archiwum i właśnie on zostanie zdekompresowany. Oto przykład otwarcia archiwum zawierającego kilka skompresowanych plików, odszukanie drugiego pliku (indeks l) i odczytanie z niego 500 bajtów:
CCompressedFile *pFile = MakeNewFile( "Nazwapliku" ); if( pFile->Open( "Nazwapliku", Cfile::modeRead ) ){ if( pFile->FindFile!nArchive( l ) ){ char cbBuffer [500]; pFile->Read( cbBuffer, 500 );
AfxMessageBox( "Wywołanie FindFileInArchive() się powiodło." );
}
else
AfxMessageBox( "Wywołanie FindFileInArchive() się nie powiodło." );
}
else
AfxMessageBox( "Błąd otwarcia pliku." ); delete pFile;
CCompressedFile::ReadByte() - Ta funkcja odczytuje pojedynczy bajt z otwartego pliku. Przykład:
CCompressedFile *pFile = MakeNewFile ( "Nazwapliku" ) ; if( pFile->Open( "Nazwapliku", CFile : :modeRead ) }{
int nData = pFile->ReadByte ( ) ;
if ( nData ==-!){
// Wystąpił błąd odczytu
}
}
delete pFile;

CcompressedFile::WriteByte() - Ta funkcja zapisuje pojedynczy bajt do otwartego pliku. Przykład:
CCompressedFile *pFile = MakeNewFile ( NULL, HUFFMAN ) ; if( pFile->Open( "Nazwapliku", CFile : :modeWrite | CFile: rmodeCreate ) ) {
pFile->WriteByte ( 'C1 ) ; } delete pFiłe;

CCompressedFile: :Read() - Ta funkcja odczytuje z pliku do bufora określoną ilość bajtów. Zwraca ilość rzeczywiście odczytanych bajtów. Funkcja działa prawie tak samo jak funkcja CFile : : Read ( ) . Przykład:
CCompressedFile *pFile = MakeNewFile ( "Nazwapliku" ) ; if( pFile->Open( "Nazwapliku", CFile : :modeRead ) ){
char cbBuffer[500] ;
pFile->Read( cbBuffer, 500 ) ; } delete pFile;

CcompressedFile: :Write() - Ta funkcja zapisuje do pliku określoną ilość bajtów. Funkcja działa prawie tak samo jak funkcja CFile : : write ( ) . Przykład:
CCompressedFile *pFile = MakeNewFile ( NULL, HUFFMAN }; if( pFile->Open( "Nazwapliku", CFile : :modeWrite | CFile: :modeCreate ) ) {
static char szStringf] = "To jest test";
pFile->Write ( szString, strlen (szString) ) ; } delete pFile;
Działanie programu CompressDemo
W tej sekcji omawiamy sposób wykorzystania funkcji składowych klasy CCompressedFile przez zastosowanie ich w przykładowym programie CompressDemo.
Pełny kod źródłowy aplikacji CompressDemo znajduje się na dołączonej do książki płytce CD-ROM, w folderze Rozdzl5\CompressDemo.
Otwieranie pliku
Kod ładujący plik z dysku do pamięci znajduje się w klasie ccompressDemoview, w jej funkcji onDrawO . Przy każdym otwarciu pliku tworzone i rysowane jest nowe okno potomne MDI. Plik jest ładowany przy pierwszym wywołaniu funkcji OnDraw ().
Po załadowaniu pliku w oknie dokumentu wyświetlane są informacje na jego temat. Dzięki temu użytkownik może poznać szczegóły dotyczące pliku, jego rozmiaru, metody kompresji oraz współczynnika kompresji. Pierwszą rzeczą wykonywaną w funkcji OnDraw () jest sprawdzenie, czy plik został załadowany. Jeśli plik został załadowany, zmienna m_bLoaded ma wartość TRUE.
Po otrzymaniu od klasy dokumentu nazwy pliku, za pomocą statycznej funkcji Make-NewFile () tworzony jest obiekt klasy CCompressedFile. Po załadowaniu pliku program sprawdza, czy plik został wcześniej skompresowany za pomocą którejś z metod kompresji klasy CCompressedFile. Dzieje się to w sposób niewidoczny dla programisty, miło jednak wiedzieć, że nie trzeba się już tym martwić.
Dla odczytywanych danych jest alokowany bufor. Rozmiar potrzebny dla nieskompre-sowanych danych można odczytać ze zmiennej składowej m_dwuncompressedsize klasy CCompressedFile. Po zaalokowaniu bufora następuje pojedyncze wywołanie funkcji Read(). Funkcja ReadO wygląda tak samo jak funkcja ReadO klasy CFile. Wymaga podania dwóch argumentów: wskaźnika do bufora oraz ilości bajtów do wczytania.
W klasie widoku zostają zachowane długości skompresowanego i nieskompresowanego pliku, pochodzące ze zmiennych składowych klasy CCompressedFile. Te informacje są potrzebne po to, by po usunięciu obiektu CCompressedFile w dalszym ciągu można było wyświetlać w oknie poprawne informacje. Plik jest zamykany, po czym obiekt jest usuwany. W aplikacji nie są wykonywane już żadne inne operacje odczytu za pomocą klasy CCompressedFile.
Informacje o pliku są wyświetlane na końcu funkcji OnDraw ().Należą do nich: nazwa pliku, rodzaj kompresji, rozmiary plików skompresowanego inieskompresowanego, współczynnik kompresji oraz status operacji odczytu.

Zapisywanie plików
Zapisywanie plików także odbywa się w klasie ccompressDemoview, w funkcji OnFile-SaveAs (). Po pierwsze, funkcja upewnia się, że bufor danych jest już zaalokowany i że dane nie mają zerowej objętości. Jeśli któryś z tych warunków nie jest spełniony, wywołanie funkcji zawodzi.
Następną rzeczą jest utworzenie za pomocą statycznej funkcji MakeNewFile () nowego obiektu klasy CCompressedFile. Jako drugi argument funkcji podawany jest rodzaj stosowanej kompresji.
Następuje wywołanie funkcji open(). Jest ona wywoływana tak samo jak funkcja Open() klasy CFile. Pierwszym argumentem jest nazwa pliku, zaś drugim znaczniki określające sposób jego otwarcia. Jeśli otwarcie pliku się powiedzie, funkcja zwraca wartość TRUE.
Zapis wszystkich danych do pliku następuje w wyniku pojedynczego wywołania funkcji write (). Jest ona wywoływana dokładnie tak samo jak funkcja write () klasy CFile. Pierwszym argumentem jest wskaźnik do bufora, zaś drugim wartość całkowita określająca ilość bajtów przeznaczonych do zapisania.
Następnie aktualizowane są zmienne widoku w celu odwzorowania zmian w stanie pliku, rozmiarze lub rodzaju kompresji.
Otwieranie skompresowanego pliku
Aby otworzyć skompresowany plik, wykonaj poniższe kroki:
1. Zacznij od utworzenia za pomocą statycznej funkcji MakeNewFile (} nowego obiektu klasy ccompressedFiie. Pierwszy argument nie jest potrzebny. Jest nim nazwa pliku, ale zwykle będziesz ją podawał dopiero przy otwarciu pliku. Drugim argumentem jest rodzaj kompresji. Może nim być HUFF, LZSS, LZW lub UNCOMP. Jeśli nie przekażesz żadnej wartości, domyślną metodą kompresji będzie LZW:
CCompressedFile *pFile = MakeNewFile( "TEST.TXT" );
2. Następnie musisz otworzyć plik. Oto kod otwierający plik TEST. TXT:
pFile->Open( "TEST.TXT", CFile::modeRead );
Jeśli dynamicznie alokujesz bufor, do którego będą odczytywane dane, użyj zmiennej pFile->m_dwUncompressedSize w celu określenia rozmiaru tego bufora. Poniższy kod alokuje bufor do odczytu:
char *pBuffer = new char[pFile->m_dwUncompressedSize];
3. Teraz za pomocą funkcji Read (} możesz odczytać dane. Oto przykład: pFile->Read( pBuffer, pFile->m_dwUncompressedSize );
4. Na koniec musisz zamknąć plik:
pFile->Close(}
Zapisywanie skompresowanego pliku
Aby zapisać skompresowany plik, wykonaj poniższe kroki:
1. Także w tym przypadku zacznij od utworzenia za pomocą statycznej funkcji MakeNewFile () nowego obiektu klasy CCompressedFile:
CCompressedFile *pFile = MakeNewFile( NULL, LZW );
2. Następnie musisz otworzyć plik. Oto kod otwierający plik o nazwie TEST.CMP:
pFile->Open( "TEST.CMP", CFile: :modeWrite | CFile:rmodeCreate) ;
3. Teraz za pomocą ftmkcji Write () możesz zapisywać dane. Oto kod:
char szEuffer[] = "To jest test klasy kompresji plików. pFile->Write( szBuffer, strlenf szBuffer ) );
4. Na koniec musisz zamknąć plik:
pFile->Close();
Klasa CSerial
dla komunikacji szeregowej
Zdalna komunikacja znalazła powszechne zastosowanie w firmowych aplikacjach, które służą do śledzenia stanu zasobów i transakcji w sieci wielu oddziałów. Na przykład, sam kiedyś pisałem wielki program związany z dystrybucją leków, w którym wiele oddziałów klinicznych wieczorami automatycznie składało zamówienia na leki na podstawie informacji o transakcjach nawiązanych w ciągu dnia. Każdego wieczora główny system odbierał od każdego z oddziałów informacje o zapotrzebowaniu na leki. Większość czasu związanego z tworzeniem tej aplikacji poświęciłem na pisanie procedur telekomunikacyjnych, które wysyłały i odbierały pakiety informacji. Jeśli miałbym wtedy pod ręką bibliotekę funkcji zawartą na dołączonej do tej książki płytce CD-ROM, czas mojej pracy znacznie by się skrócił.
Następne sekcje opisują klasę służącą do obsługi łącza szeregowego poprzez porty COM komputera. Dzięki niej możesz wzbogacić swoje aplikacje o wszystkie informacje dostępne w świecie połączonych komputerów.
Komunikacja szeregowa
Korty komunikacyjne komputera PC korzystają z komunikacji asynchronicznej. Każdy bajt danych stanowi potencjalnie osobną jednostkę. Komputer wysyłający dane może zatrzymać się co drugi bajt, jednak komputer odbierający może odczytywać dane tak szybko, jak się pojawiają.
Aby to osiągnąć, dane asynchroniczne wymagają zastosowania dodatkowego bitu oznaczającego początek nowego bajtu (bit startu) oraz dodatkowego bitu oznaczającego koniec bajtu (bit stopu). Modem o prędkości 9600 bodów może przekazać jedynie 960 bajtów danych na sekundę, gdyż każdy z nich wymaga przesłania co najmniej dziesięciu bitów.
Szybkie, nowoczesne modemy w rzeczywistości nie wysyłają bitów startu i bitów stopu. Zostają one odrzucone w wyniku ogólnej kompresji danych. Jednak bity startu i stopu są generowane na przewodzie łączącym port COM z modemem zewnętrznym (poprzez interfejs RS-232). Z tego powodu oraz z powodu ogólnej dostępności kompresji danych nowoczesne porty COM są zwykle skonfigurowane do użycia większej prędkości transmisji (pomiędzy portem COM a modemem) niż aktualna prędkość transmisji danych (pomiędzy dwoma modemami poprzez linię telefoniczną). Na przykład, modemy mogą pracować z szybkością 14400 bitów na sekundę, lecz port COM jest skonfigurowany do pracy z prędkością 38400 bitów na sekundę.
Stanowi to jedynie przykład dostosowywania starych konwencji do nowych wymagań. Standard komunikacji szeregowej pochodzi z czasów poprzedzających same komputery. Pierwsze modemy były używane przez dalekopisy do przesyłania kablogramów. Gdy powstały pierwsze komputery, istniejąca liczba dalekopisów zapewniała wygodne terminale.
Te standardy miały sens do momentu, w którym stało się możliwe umieszczanie procesorów bezpośrednio w modemach. "Inteligentne" modemy mogą wybierać numer, wysyłać faksy, kompresować dane i korygować błędy transmisji. Dawnym projektantom standardów takich jak RS-232 nawet się nie śniło o żadnej z tych funkcji. Tak więc, mimo że reguły zostały dostosowane do nowoczesnych technologii, część starych ograniczeń wciąż gdzieniegdzie się pojawia.
Klasa CSerial
Ta sekcja zawiera opis funkcji składowych klasy CSerial. Większość opisów funkcji zawiera przykłady ilustrujące sposób zastosowania danej funkcji.
* CSerial::CSerial() - to jest konstruktor klasy CSerial; nie wymaga podania żadnych parametrów i inicjalizuje wszystkie zmienne składowe klasy.
* CSerial::Open() - ta funkcja składowa otwiera port komunikacyjny. Wymaga podania dwóch parametrów: numeru portu komunikacyjnego (zwykle od O do 4) oraz prędkości transmisji w bitach na sekundę (dozwolone wartości to 300, 1200, 2400, 4800, 9600, 19200, 38400 oraz 76800). Funkcja zwraca wartość logiczną wskazującą sukces (TRUE) lub porażkę (FALSE). Oto przykład otwarcia portu komunikacyjnego COM2 dla prędkości 9600 bodów:
CSerial Serial;
if( Serial.Open( 2, 9600 ) )
AfxMessageBox( "Port otworzony poprawnie." ); else
AfxMessageBox( "Błąd otwarcia portu." );
4- CSerial::Close() - ta funkcja zamyka port komunikacyjny. Wywołuje ją destruktor klasy CSerial, więc nie musisz jej wywoływać jawnie, chyba że z jakiegoś powodu chcesz zamknąć port przed zniszczeniem obiektu. Obie poniższe funkcje tworzą obiekt klasy CSerial. Pierwsza z nich zamyka port jawnie, zaś druga pozostawia to konstruktorowi:
void FunctionOne( void )
{
CSerial Serial; if( Serial.Open{2, 9600 } ) Serial.Close(); // Jawne zamknięcie portu
}

void FunctionTwo( void ) {
CSerial Serial;
Serial.Open( 2, 9600 );
// Zamknięcie portu pozostawione konstruktorowi
}
CSerial::SendData() - ta funkcja zapisuje dane z bufora do portu szeregowego. Pierwszym argumentem (const char*) jest bufor zawierający dane przeznaczone do wysłania. Drugim argumentem jest ilość bajtów przeznaczonych do wysłania. Funkcja zwraca ilość bajtów rzeczywiście zapisanych do portu. Oto przykład otwarcia portu komunikacyjnego i wysłania do niego ciągu znaków:
void Function ( void )
{
CSerial Serial;
if( Serial.Open( 2, 9600 ) ){
static char *szMessage[] = "To jest testowa wiadomość";
int nBytesSent;
nBytesSent = Serial.SendData( szMessage, strlen( szMessage ) );
}
Serial.Close();
}
CSerial::ReadDataWaiting() - ta funkcja zwraca ilość otrzymanych bajtów danych oczekujących w buforze portu komunikacyjnego. Nie wymaga podawania żadnych argumentów.
CSerial::ReadData() - ta funkcja odczytuje otrzymane dane z bufora portu komunikacyjnego. Pierwszym argumentem (void *) jest bufor na odczytywane dane, zaś drugim jest rozmiar bufora. Na przykład, jeśli zaalokowany bufor ma rozmiar 500 bajtów, tą wartością powinno być 500. Zabezpiecza to bufor przed przepełnieniem. Funkcja ReadData () zwraca wartość określającą rzeczywistą ilość odczytanych bajtów. Oto przykład otwarcia portu szeregowego i odczytania do bufora oczekujących danych:
void Function ( void )
{
CSerial Serial;
if( Serial.Open( 2, 9600 ) }{
char *lpBuffer = new [500];
int nBytesRead;
nBytesRead = Serial.ReadData( IpBuffer, 500 );
delete [] IpBuffer;
}
Serial.Close();
}
Plik .cpp i .// dla klasy CSerial znajdziesz na dołączonej do książki płytce CD-ROM, w kartotece Rozdzl5\Serial. Aby ich użyć, po prostu skopiuj je do kartoteki projektu i dodaj do projektu.
Klasa CRegistry
Ten tajemniczy stwór pojawił się w Windows 3.1 wraz ze wprowadzeniem OLE. Bez względu na to, jak bardzo zatwardziali programiści starają się go unikać, jednak istnieje i ma się dobrze; w rzeczywistości, gdy zwracaliśmy uwagę na coś innego, po cichu przejął między innymi rolę plików inicjalizacyjnych, czyli plików INI.
Rejestr jest hierarchicznie zorganizowanym składem informacji. Każda pozycja w tej podobnej do drzewa strukturze jest nazywana kluczem (ang. key). Klucz może zawierać dowolną ilość podkluczy oraz pozycje danych zwane wartościami (ang. value). W tej postaci Rejestr przechowuje informacje o systemie, jego konfiguracji, urządzeniach sprzętowych oraz aplikacjach. Przejął także rolę plików INI, zapewniając miejsce przechowywania informacji specyficznych dla poszczególnych aplikacji. Klucze Rejestru są identyfikowane poprzez nazwy. Nazwa klucza składa się z drukowalnych znaków ASCII z wyjątkiem odwrotnego ukośnika (\), spacji i znaków wildcard (* oraz ?). Nazwy kluczy zaczynające się od kropki (.) są zarezerwowane. W nazwach kluczy nie ma znaczenia wielkość liter.
Wartości Rejestru
Wartość w Rejestrze jest identyfikowana na podstawie nazwy. Nazwy wartości zawierają te same znaki co nazwy kluczy. Same wartości mogą zawierać łańcuchy, dane binarne lub 32-bitowe wartości bez znaku.
Ogólnie rzecz biorąc, przechowywanie w Rejestrze danych o rozmiarach większych niż kilobajt lub dwa, nie jest zalecane. W przypadku większych elementów użyj oddzielnego pliku, zaś w Rejestrze przechowaj jedynie jego nazwę. W Windows 95/98 rozmiar danych w wartościach Rejestru jest ograniczony do 64K. Ważne jest także, aby pamiętać, że zwykle przechowanie klucza wymaga więcej miejsca niż przechowanie wartości. Jeśli to tylko możliwe, umieszczaj wartości wewnątrz wspólnego klucza zamiast korzystać w tym samym celu z kilku kluczy.
Predefiniowane klucze Rejestru
Rejestr zawiera kilka predefiniowanych kluczy:
* HKEY_LOCAL_MACHINE zawiera pozycje opisujące komputer i jego konfigurację. Obejmują one informacje o procesorze, płycie systemowej oraz zainstalowanym sprzęcie i oprogramowaniu.
4 HKEY_CLASSES_ROOT stanowi tylne wejście dla informacji odnoszących się do dokumentów oraz mechanizmu OLE/COM. Ten klucz stanowi po prostu część klucza HKEY_WLOCAL_MACHINE (a mianowicie podklucz HKEY_LOCAL_MACHINE \SOFTWARE\classes). Przechowywane tu informacje są używane głównie przez aplikacje OLE/ActiveX oraz aplikacje powłoki, takie jak Menedżer programów, Menedżer plików i Eksplorator Windows.
HKEY_USERS pełni funkcje klucza przechowującego ustawienia dla domyślnego użytkownika oraz innych użytkowników komputera.
HKEY_CURRENT_USER jest kluczem zawierającym informacje odnoszące się do preferencji bieżącego (zalogowanego do systemu) użytkownika.
W Windows 95/98 występują dwa dodatkowe predefiniowane klucze:
HKEY_CURRENT_CONFIG zawiera informacje o bieżących ustawieniach konfi-guracyjnych systemu.
HKEY_DYN_DATA zapewnia dostęp do dynamicznych informacji stanu, na przykład takich jak informacje o urządzeniach plug-and-play.
Powszechnie wykorzystywane klucze Rejestru
Informacje o kluczach Rejestru często są trudne do zdobycia. Z tego powodu zdecydowałem się na zgromadzanie informacji o kilku często wykorzystywanych kluczach, które mogą mieć znaczenie dla programisty.
Wewnątrz klucza HKEY_LOCAL_MACHINE znajduje się kilka podkluczy. Zawierają one informacje o programowej i sprzętowej konfiguracji komputera. Spomiędzy nich klucze Conf ig i Enum są specyficzne dla Windows 95/98 i odnoszą się do możliwości plug-and-play tego systemu. W podkluczu Config są przechowywane różne informacje o konfiguracji sprzętowej; podklucz Enum zawiera numeratory magistral Windows 95/98 budujące drzewo urządzeń sprzętowych.
Wewnątrz klucza HKEY_LOCAL_MACHINE znajduje się również podklucz System. Podklucz System\CurrentControlSet zawiera informacje konfiguracyjne dla usług i sterowników urządzeń.
Kolejne podklucze wewnątrz klucza HKEY_LOCAL_MACHINE to Software i ciasses. Podklucz Software zawiera informacje o zainstalowanych pakietach oprogramowania. Podklucz ciasses jest kluczem wskazywanym przez predefiniowany klucz HKEY_CLASSES_ROOT. Podklucz Software ma szczególne znaczenie dla programistów, gdyż służy właśnie do przechowywania informacji konfiguracyjnych i instalacyjnych specyficznych dla aplikacji. Microsoft zaleca budowanie w nim serii podkluczy, reprezentujących nazwę firmy, nazwę produktu oraz jego wersję.
To, co umieścisz wewnątrz takiego podklucza, jest całkowicie zależne od aplikacji. Nie powinieneś tam jednak przechowywać niczego, co jest specyficzne dla użytkownika. Takie informacje powinny znaleźć się w podkluczu wewnątrz klucza HKEY_CURRENT_USER.
Szczególnie interesujący jest klucz HKEY_LOCAL_MACHiNE\Software\Microsoft\ windows\CurrentVersion, opisujący bieżącą konfigurację Windows.
Podklucze w HKEY_CLASSES_ROOT
Klucz HKEY_CLASSES_ROOT zawiera dwa rodzaje podkluczy: podklucze odnoszące się do rozszerzeń nazw plików oraz podklucze definicji klas. Podklucz rozszerzenia nazwy pliku ma nazwę odpowiadającą temu rozszerzeniu (na przykład .doć). Taki klucz zwykle zawiera jedną wartość bez nazwy, przechowującą nazwę podklucza definicji klasy. Podklucz definicji klasy opisuje zachowanie klasy dokumentu. Przechowywane informacje obejmują dane dotyczące poleceń powłoki oraz właściwości związane z OLE. Podkluczem klucza HKEY_CLASSES_ROOT jest także CLSID. Przechowywane są w nim identyfikatory klas COM. Gdy za pomocą AppWizarda tworzysz aplikację MFC, tworzona jest również seria podkluczy wewnątrz klucza HKEY_CLASSES_ROOT. Wiążą one rodzaj dokumentu i jego rozszerzenie z nową aplikacją oraz określają właściwości OLE, takie jak identyfikator klasy OLE.
Podklucze w HKEY_USERS
Klucz HKEY_USERS zawiera podklucz o nazwie De f auł t oraz ewentualnie kilka podkluczy reprezentujących poszczególnych użytkowników systemu. Podklucz Default odnosi się do domyślnego profilu użytkownika. Pozostałe pozycje zawierają profile użytkowników systemu.
Podklucze w HKEY_CURRENT_USER
Klucz HKEY_CURRENT_USER reprezentuje profil aktualnie zalogowanego użytkownika. Informacje o konfiguracji aplikacji odnoszące się do bieżącego użytkownika powinny być przechowywane właśnie w tym kluczu, w jego podkluczu Software. W tym celu powinieneś stworzyć serię podkluczy określających nazwę firmy, nazwę produktu oraz jego wersję.
Działanie klasy CRegistry
W tej sekcji znajduje się opis funkcji składowych klasy CRegistry.
Aby ułatwić Ci dostęp do Rejestru, stworzyliśmy specjalną klasę o nazwie CRegistry. Jej kod źródłowy znajdziesz na dołączonej do książki płytce CD-ROM, w kartotece Rozdzl5\Registry. Aby wykorzystać klasę w swoim projekcie, po prostu skopiuj pliki do swojej kartoteki, a następnie dołącz je do projektu. Poniżej znajdziesz opis sposobu użycia funkcji składowych wraz z odpowiednimi przykładami.
CRegistry::CRegistry() - istnieją dwie formy konstruktora klasy CRegistry. Pierwsza z nich tworzy obiekt CRegistry i nie wymaga podania żadnych argumentów, zaś druga tworzy obiekt CRegistry i próbuje otworzyć klucz podany jako para argumentów. Oto oba przykłady:
CRegistry Registry;
// Tworzy jeszcze nie otwarty obiekt CRegistry
lub
CRegistry Registry( HKEY_LOCAL_MACHINE,
"Software\\GreatSoftware" );
// Tworzy obiekt CRegistry i próbuje otworzyć klucz
SOFTWARE\GreatSoftware.

CRegistry::0pen() - ta funkcja otwiera klucz Rejestru. Wymaga podania dwóch argumentów: nazw podklucza oraz nazwy zawartego w nim podklucza, który chcesz otworzyć. Jeśli otwarcie klucza się powiedzie, funkcja zwróci wartość TRUE, w przeciwnym przypadku zwróci wartość FALSE. Oto przykład:
CRegistry Registry;
if( Registry.Open( HKEY_LOCAL_MACHINE,
"Software\\GreatSoftware" ) )
AfxMessageBox("Powiodło się otwarcie klucza Rejestru."); else
AfxMessageBox("Nie powiodło się otwarcie klucza Rejestru.");

CRegistry::Close() - ta funkcja zamyka uprzednio otwarty klucz Rejestru. Nie musisz jej jawnie wywoływać, gdyż wywołuje j ą destruktor klasy CRegistry.
CRegistry::lsOpen() - funkcja zwraca wartość TRUE, jeśli klucz Rejestru jest już otwarty.
CRegistry::ReadDWORD() - funkcja odczytuje wartość DWORD z otwartego klucza Rejestru. Pierwszym jej argumentem jest nazwa wartości. Drugi argument to wskaźnik do zmiennej typu DWORD, w której zostanie umieszczona odczytana wartość. Trzeci, opcjonalny argument to wskaźnik do zmiennej typu DWORD, w której ma zostać umieszczony kod błędu. Jeśli odczyt wartości się powiedzie, funkcja zwraca wartość TRUE. W tym przykładzie otwieramy klucz Rejestru i odczytujemy wartość DWORD:
CRegistry Registry;
DWORD dwValue;
if( Registry.Open( HKEY_LOCAL_MACHINE,
"Software\\GreatSoftware" ) )
Registry.ReadDWORD( "WindowColor", &dwValue );

CRegistry::ReadString() - funkcja odczytuje łańcuch znaków z otwartego klucza Rejestru. Pierwszym jej argumentem jest nazwa wartości. Drugi argument to wskaźnik do bufora, w którym zostaną umieszczone odczytane znaki. Trzeci argument to rozmiar przygotowanego bufora. Czwarty, opcjonalny argument to wskaźnik do zmiennej typu DWORD, w której ma zostać umieszczony kod błędu. Jeśli odczyt łańcucha znaków się powiedzie, funkcja zwraca wartość TRUE. W tym przykładzie otwieramy klucz Rejestru i odczytujemy z niego łańcuch znaków:
CRegistry Registry;
char cbBuffer[250];
if( Registry.Open( HKEY_LOCAL_MACHINE,
"Software\\GreatSoftware" ) )
Registry.ReadString{ "UserName", cbBuffer, 250 );
CRegistry::WriteDWORD() - funkcja zapisuje wartość DWORD do otwartego klucza Rejestru. Pierwszym jej argumentem jest nazwa wartości. Drugi argument to wartość DWORD, która ma zostać zapisana. Trzeci, opcjonalny argument to wskaźnik do zmiennej typu DWORD, w której ma zostać umieszczony kod błędu. Jeśli zapis wartości się powiedzie, funkcja zwraca wartość TRUE. W tym przykładzie otwieramy klucz Rejestru i zapisujemy wartość DWORD:
CRegistry Registry;
if( Registry.Open( HKEY_LOCAL_MACHINE,
"Software\\GreatSoftware" ) )
Registry.WriteDWORD( "WindowColor", 50 );
CRegistry::WriteString() - funkcja zapisuje łańcuch znaków do otwartego klucza Rejestru. Pierwszym jej argumentem jest nazwa wartości. Drugi argument to wskaźnik do bufora, w którym znajdują się zapisywane znaki. Trzeci opcjonalny argument to wskaźnik do zmiennej typu DWORD, w której ma zostać umieszczony kod błędu. Jeśli zapis łańcucha znaków powiedzie się funkcja zwraca wartość TRUE. W tym przykładzie otwieramy klucz Rejestru i zapisujemy do niego łańcuch znaków:
CRegistry Registry;
if( Registry.Open( HKEY_LOCAL_MACHINE,
"Software\\GreatSoftware" ) )
Registry.WriteString( "UserName", "John Doe" );
Korzystanie ze schowka
Schowek stanowi popularną w Windows metodę przekazywania danych pomiędzy dwoma procesami. Proces może umieścić w schowku pakiet danych w jednym z prede-finiowanych fonnatów (zebranych w tabeli 15.3) lub zarejestrować własny format. Ż kolei inny proces może zajrzeć do schowka i zdecydować się pobrać zdefiniowane w nim dane. Stanowi to łatwy sposób przekazywania danych do innego procesu.
Przed wstawieniem lub pobraniem danych ze schowka należy wywołać funkcję składową cwnd: : openClipboard (). Poniższy przykład ilustruje otwarcie schowka z użyciem funkcji składowej klasy cwnd, a następnie pobranie z niego danych tekstowych:
if( OpenClipboard() ){
HGLOBAL hMem = GetClipboardData( CF_TEXT ); if ( hMem != NULL ){
char *lpText = (char *) GlobalLock( hMem );
if ( lpText != NULL ) {
// Przeprowadź operacje na tekście
}
// Nie zwalaniaj uchwytu pamięci, gdyż posiada go schowek. } else
AfxMessageBox( "Nie udało się pobranie tekstu ze schowka." );
// Zamknij schowek. CloseClipboard();
}
Tabela 15.3. Predefmiowane formaty schowka
Format
Opis
CF_ BITMAP
Uchwyt bitmapy (HBITMAP).

CF_ DIB
Obiekt pamięci zawierający strukturę BITMAPINFO, po której następują dane bitmapy.

CF_ DIF
Format wymiany danych firmy Software Arts (Data Interchange Format).

CF_ DSPBITMAP
Format wyświetlania bitmapy powiązany z formatem prywatnym. Parametr hMem musi być uchwytem danych, które mogą być wyświetlone w formie bitmapy zamiast prywatnie sformatowanych danych.

CF_ DSPENHMETAFILE
Format wyświetlania metapliku powiązany z formatem prywatnym. Parametr hMem musi być uchwytem danych, które mogą być wyświetlone w formie obrazka z metapliku zamiast prywatnie sformatowanych danych.


CF_DSPMETAFILEPICT Format wyświetlania obrazka z metapliku powiązany z formatem prywatnym. Parametr hMem musi być uchwytem danych, które mogą być wyświetlone w formie obrazka z metapliku zamiast prywatnie sformatowanych danych.


CF_DSPTEKT
Format wyświetlania tekstu powiązany z formatem prywatnym. Parametr hMem musi być uchwytem danych, które mogą być wyświetlone w formie tekstu zamiast prywatnie sformatowanych danych.

CF_ENHMETAFILE
Uchwyt rozszerzonego metapliku (HENHMETAFILE).

CF_GDIOBJFIRST do CF_ GDIOBJLAST
Zakres wartości całkowitych dla zdefiniowanych przez aplikację formatów schowka dla obiektów GDI. Uchwyty powiązane z formatami schowka z tego zakresu nie są automatycznie usuwane za pomocą funkcji GlobalFree () w momencie opróżniania schowka. Także, jeśli używasz wartości z tego zakresu, parametr hMem nie stanowi uchwytu obiektu GDI, lecz uchwyt pamięci zaalokowanej funkcją GlobalAlloc () z użyciem znaczników GMEM_DISCARDABLE oraz GMEM_MOVEABLE.

CF_HDROP
Uchwyt typu HDROP identyfikujący listę plików. Aplikacja może uzyskać informacje o plikach, przekazując ten uchwyt funkcjom
DragQueryFile().

CF_LOCALE
Dana stanowi uchwyt identyfikatora języka powiązanego z tekstem w schowku. Gdy zamkniesz schowek i zawiera on dane CF_TEXT bez danej CF_LOCALE, system automatycznie ustawi format CF_LOCALE na domyślny język w systemie. Formatu CF_LOCALE możesz użyć do powiązania tekstu w schowku z innym językiem. Aplikacja wklejająca tekst ze schowka może skorzystać z tej informacji w celu wyznaczenia zestawu znaków użytego do stworzenia tekstu.

C_METAFILEPICT
Uchwyt formatu obrazka w metapliku zdefiniowanego przez strukturę METAFILEPICT. Podczas przekazywania w DDE uchwytu CF_METAFILEPICT aplikacja odpowiedzialna za usunięcie hMem musi zwolnić także metaplik związany z uchwytem
CF_METAFILEPICT.

CF_OEMTEXT
Format tekstowy zawierający znaki z zestawu OEM. Każda linia kończy się parą CR/LF (powrotu karetki i przejścia do nowej linii). Znak NULL sygnalizuje koniec danych.

CF_OWNERDISPLAY
Format danych wyświetlany przez użytkownika. Posiadacz schowka musi wyświetlać i aktualizować okno przeglądarki schowka, a także obsługiwać komunikaty WM_ASKCBFORMATNAME,
WM_HSCROLLCLIPBOARD, WM_PAINTCLIPBOARD, WM_SIZECLIPBOARD oraz WM_VSCROLLCLIPBOARD. Parametr hMem musi mieć wartość NULL.

CF_PALETTE
Uchwyt palety kolorów. Kiedykolwiek aplikacja umieszcza w schowku dane korzystające z palety lub zakładające jej istnienie, powinna w nim umieszczać także samą paletę. Jeśli schowek zawiera dane w formacie CF_PALETTE (logiczną paletę kolorów), aplikacja powinna używać funkcji SelectPalette () i RealizePalette () do zrealizowania (porównania) innych danych w schowku z użyciem tej palety logicznej. Podczas wyświetlania danych ze schowka. Schowek Windows zawsze używa jako bieżącej palety właśnie palety występującej w schowku w formacie CF_PALETTE.

CF_PENDATA
Dane dla rozszerzeń pióra Pen Computing w Microsoft Windows.

CF_PRIVATEFIRST
Zakres wartości całkowitych dla prywatnych formatów schowka. do CF_PRIVATELAST
Uchwyty powiązane z prywatnymi formatami schowka nie są zwalniane automatycznie; właściciel schowka musi je sam zwolnić, zwykle w odpowiedzi na komunikat WM_DESTROYCLIPBOARD.

CF_RIFF
Reprezentuje dane dźwiękowe bardziej złożone niż dane, które da się zaprezentować w formacie CF_WAVE, czyli standardowym formacie danych dźwiękowych.

CF_SYLK
Format Symbolic Link (SYLK) Microsoftu.
CF_TEXT
Format tekstowy. Każda linia kończy się parą CR/LF (powrotu karetki i przejścia do nowej linii). Znak NULL sygnalizuje koniec danych. Używaj tego formatu dla tekstów ANSI.

CF_WAVE
Reprezentuje dane dźwiękowe w jednym ze standardowych formatów, takich jak 11 kHz czy 22 kHz z modulacją PCM (pulse code modulation).

CF_TIFF
Format pliku TIFF (tagged image file format)

CF_UNICODETEXT
Tylko w Windows NT: Format tekstu Unicode. Każda linia kończy się parą CR/LF (powrotu karetki i przejścia do nowej linii). Znak NULL sygnalizuje koniec danych.



Umieszczenie danych w schowku jest prawie tak samo łatwe. Ważna różnica polega na tym, że po wywołaniu funkcji OpenClipboard () musisz wywołać także funkcję EmptyClipboardO. Musisz także zaalokować uchwyt pamięci globalnej, do którego skopiujesz dane (chyba że dane już są zawarte w pamięci globalnej). Gdy w schowku zostanie umieszczony uchwyt pamięci globalnej, nie powinien być zwalniany. Umieszczanie tekstu w schowku ilustruje poniższy przykład:
if( OpenClipboard() ){
static char szMessage[] = "To jest test";
// Zaalokuj miejsce na tekst i kończący znak NULL. HGLOBAL hMem = GlobalAlloc( GMEM_MOVEABLE,
strlen( szMessage ) +1 );
// Zablokuj zaalokowaną pamięć, skopuj do niej tekst, po
// czym ją zwolnij.
char *lpDest = (char *) GlobalLock( hMem );
strcpy( IpDest, szMessage );
GlobalUnlock( hMem );
// Opróżnij schowek i umieść w nim dane tekstowe. EmptyClipboard() ; SetClipboardDąta( CF_TEXT, hMem );
// Nie zwalniaj hMem, gdyż należy do schowka
// Zamknij schowek. CloseClipboardO ;
}
Podsumowanie
W tym rozdziale nauczyłeś się zarządzać wieloma formami danych, włącznie z łańcuchami znaków, plikami, danymi przekazywanymi przez łącze szeregowe, danymi w Rejestrze systemowym oraz w systemowym schowku. Wszystkie te techniki znacząco wzbogacą Twój arsenał narzędzi programisty.
Nie powinieneś mieć już trudności w napisaniu programu operującego łańcuchami i przeszukującego je. Jak widzieliśmy, bardzo ułatwia to klasa cstring. Widziałeś także jak za pomocą klasy CFile łatwo można przeprowadzać operacje na plikach. Jako premię otrzymałeś klasę cserial pozwalającą na szybkie wykorzystanie komunikacji szeregowej oraz klasę CRegistry, dającą łatwy dostęp do danych konfiguracyjnych zapisanych w Rejestrze.

Wyszukiwarka

Podobne podstrony:
5 Mathcad Zapis i odczyt danych
zapis i odczyt z pliku macierz
Zapis i odczyt
zapis i odczyt pliku
Zajęcia 3 Odczyt i zapis dokumentów XML
Spróbuj odczytać Wesele Wyspiańskiego jako zapis świadomości
Spróbuj odczytać Wesele Wyspiańskiego jako zapis świadom~4C4
15 3
15

więcej podobnych podstron