Image
27.6.2016 0 Comments

C++ pod Windows / Súbory a serializácia / 19. časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

Keď čítate tieto riadky, nové Visual Studio je už realitou. Uvidíme, ako sa uchytí, každopádne každý, kto chce zostať „in“, by s ním mal začať pracovať čo najskôr. Niektorí ste sa ma v súvislosti s Microsoft .NET (ktorý získava na dôležitosti práve s uvedením nového Visual Studia)  pýtali, či vôbec ešte má zmysel učiť sa veci ako MFC, ATL (Active Template Library), COM (Component Object Model) a pod. Moja odpoveď je áno. .NET síce mení tradičný spôsob vývoja aplikácií (a aplikácie samotné), ale s týmito vedomosťami pochopíte .NET rýchlejšie a rýchlejšie začnete aj vyvíjať .NET aplikácie. Samozrejme, netvrdím, že klasickému programovaniu Win32 „odzvonilo“, práve naopak, myslím si, že ešte dlho bude hrať dôležitú úlohu. Preto sa nebojte, určite ešte budete mať možnosť vedomosti o programovaní Win32 nejako zhodnotiť :-).  

V tejto a nasledujúcej časti si preberieme prácu so súbormi v prostredí Windows (samozrejme využitím MFC). Po jej prečítaní by ste mali byť schopní uložiť/čítať dáta (objekty, kolekcie atď.) na/z disk(u). Ako príklad si vytvoríme jednoduchý textový editor, ktorý si budete môcť ďalej rozšíriť a vytvoriť tak pre prax užitočnú aplikáciu. 

MFC a I/O operácie. Keď sa rozpamätáte na časy klasického C-čka a operačného sytému DOS, práca so súbormi nepredstavovala veľký problém. Pomocou funkcie fopen sa súbor otvoril, fgets alebo fscanf prečítali dáta a fputs alebo fprintf dáta zapísali. Nakoniec sa ešte súbor pomocou fclose zatvoril a bolo to vybavené. V prostredí Windows to nie je inak, len sa zmenili mená funkcií a vďaka výnimkám sa zlepšila robustnosť výsledných aplikácií. Knižnica MFC nám ponúka dva prostriedky na prácu so súbormi. Prvým je trieda CFile a druhým na nej založená serializácia. 

Základy I/O operácií v MFC – trieda Cfile. Trieda CFile predstavuje najjednoduchší spôsob, ako pracovať so súbormi v prostredí Windows. V helpe sa dočítate, že táto trieda sprostredkúva nebufferované I/O operácie na binárnej úrovni. Cez jej potomkov podporuje napr. prácu s textovými súbormi, súbormi mapovanými do pamäte, „socketovými“ súbormi (také, ktoré zabezpečujú komunikáciu v sieti) a inými typmi súborov. My si ukážeme prácu s najvyššou vrstvou – triedou CFile.  Ak ju zvládnete, práca s jej potomkami vám nebude robiť problémy.

Príklad 1 – trieda CFile. Na príklade si ukážeme čítanie a zapisovanie do súboru. Vytvorte čisté Workspace s názvom Files a pridajte doň nový projekt (Win32 Console Application s podporou MFC) s názvom ClassFile. Vetvu else vo funkcii _tmain zmeňte takto:

char* pszFileName = "c:\\pokus.txt";
char* zapisat="Ahoj svet!";
char citat[11];
 
try
{
      // skonstruujeme objekt CFile typu myFile
            CFile myFile(pszFileName, CFile::modeCreate | CFile::modeReadWrite);
      // zapisujeme do suboru
      myFile.Write(zapisat, 11);
      // ideme na zaciatok
      myFile.Seek(0, CFile::begin);
      // citame zo suboru
int pocet=myFile.Read(citat, 11);
      // vypiseme precitane data
      cout << citat;
      // zavrieme subor
myFile.Close();
}
catch(CFileException *e)
{
      // zistime podrobnosti o pricine vynimky
      ::AfxThrowFileException(e->m_cause, e->m_lOsError, pszFileName); 
}

Ako prvé skonštruujeme objekt typu CFile (myFile). Tento typ konštruktora nám súbor odovzdaný ako prvý parameter (pszFileName) aj otvorí, preto volaním funkcie Write môžeme priamo zapisovať dáta. Druhým parametrom konštruktora môžeme bližšie špecifikovať prístupové práva na súbor (Read, Write, Read/Write...). Viacero špecifikátorov potom oddeľujeme pomocou bitového OR (|) (zoznam najpoužívanejších nájdete v tab. 1).

Špecifikátor

Opis

CFile::modeCreate

Vytvorí nový súbor. Ak súbor už existuje, jeho obsah sa zmaže

CFile::modeNoTruncate

Vytvára nový súbor. Ak súbor už existuje, jeho obsah sa nezmaže

CFile::modeRead

Otvorí súbor na čítanie

CFile::modeReadWrite

Otvorí súbor na čítanie a zápis

CFile::modeWrite

Otvorí súbor na zápis

CFile::modeNoInherit 

Zabraňuje, aby súbor bol zdedený „detskými“ procesmi

CFile::shareDenyNone

Dovoľuje ostatným procesom čítať/zapisovať do súboru

CFile::shareDenyRead

Zakáže ostatným procesom čítať zo súboru

CFile::shareDenyWrite

Zakáže ostatným procesom zapisovať do súboru

CFile::shareExclusive

Nedovoľuje ostatným procesom manipulovať so súborom (Read/Write)

 Tab. 1 Zoznam najpoužívanejších špecifikátorov triedy CFile

Vo výpise kódu si všimnite presun na začiatok súboru pomocou funkcie Seek. Tento presun je nevyhnutný, inak by sme čítali neexistujúce dáta, čo by nám asi veľmi nepomohlo. Ak by akákoľvek operácia v bloku try zlyhala, zachytí sa výnimka typu CFileException, ktorú obslúžime v bloku catch.  Funkcia AfxThrowFileException  nám podá vysvetlenie, prečo došlo k zlyhaniu (v helpe si môžete pozrieť bližší opis všetkých chybových stavov).

V príklade sme si neukázali použitie členských funkcií triedy CFile na zisťovanie stavu súboru (jeho názov, cestu, čas a dátum vytvorenia atď). Tieto funkcie sú dobre opísané v helpe, preto im nebudeme zbytočne venovať miesto.

SERIALIZÁCIA. Azda každá „lepšia“ aplikácia ponúka uloženie, resp. načítanie dát do svojho programu z disku, prípadne iného úložného média (storage medium). Často potrebujeme zapísať/čítať stav objektu (stav objektu je obyčajne daný hodnotou jeho členských prermenných) na/z disk(u). Tento proces zapisovania a čítania „stavu“ objektu na/z disk(u)  sa nazýva serializácia. Ako som už uviedol, serializácia je založená na triede CFile. Nespomenul som však triedu CArchive, ktorá predstavuje medzivrstvu medzi serializáciou a triedou CFile (obr. 1) a je na fungovanie serializácie nevyhnutná.

Obr. 1  Vrstvy serializácie

Bližší opis triedy CArchive je v helpe, praktická ukážka jej použitia je v príklade 2. Všetky tieto vrstvy sa obyčajne stretávajú vo funkcii Serialize (virtuálna členská funkcia triedy CObject ), ktorú si prepíšete v triede, ktorej objekt chcete serializovať (ak budete využívať len objekt CArchive, funkciu Serialize síce prepisovať nemusíte, ale to vám veľmi neodporúčam – pozri príklad 2). Ďalšou podmienkou funkčnosti serializácie je, že trieda, ktorej objekt chcete serializovať, je odvodená od CObject a musí obsahovať makro DECLARE_SERIAL v hlavičkovom súbore a makro IMPLEMENT_SERIAL v implementačnom súbore. Ak máte štandardnú SDI (resp. MDI) aplikáciu, implementáciu serializácie do tejto aplikácie vám aplikačný systém výrazne zjednoduší. Ale ak máte iný typ aplikácie, musíte sa o všetko postarať sami. Ukážeme si obidva prípady. Príklad 2 predstavuje „manuálnu“ serializáciu, príklad 3 zase serializáciu aplikácie SDI s využitím všetkých výhod aplikačného systému (ten si ukážeme až v ďalšej časti).

Makrá DECLARE_SERIAL a IMPLEMENT_SERIAL. V 16. časti sme sa stretli s makrami DECLARE_DYNAMIC IMPLEMENT_DYNAMIC, ktoré sprístupňovali run-time informácie o triede. Makrá DECLARE_SERIAL a IMPLEMENT_SERIAL majú podobnú funkciu, a síce sprístupňujú serializáciu pre triedu, v ktorej sa nachádzajú (robia triedu serializovateľnou). Zaujímavá je syntax makra IMPLEMENT_SERIAL, ktoré ako svoj tretí parameter dostáva verziu objektu konkrétnej triedy. Toto číslo (väčšie alebo rovné 0) sa nazýva číslo schémy objektu a zabraňuje pri načítavaní stavu objektu do pamäte načítať stav objektu s inou schémou, ako je schéma jeho triedy v pamäti. Tým sa zabezpečí načítanie správnej verzie objektu (podrobne pozri help). 

Príklad 2 – manuálna serializácia jednoduchého objektu. Do Workspace Files pridajte nový projekt – Win 32 Console Application s podporou MFC. Názov projektu: BasicSerial. Pridajte novú triedu (generic class) CStudentSerial. Aby sme túto triedu urobili serializovateľnou, odvoďte ju od CObject:

class CStudentSerial : public CObject

Pridajte makro DECLARE_SERIAL a prepíšte funkciu Serialize

DECLARE_SERIAL(CStudentSerial)
void Serialize(CArchive& archive);

To sú zmeny v hlavičkovom súbore. Do zdrojového (implementačného) súboru pridajte makro IMPLEMENT_SERIAL:

IMPLEMENT_SERIAL(CStudentSerial, CObject, 1)

A telo funkcie Serialize:

void CStudentSerial::Serialize(CArchive& archive)
{
       // najprv zavolame povodnu funkciu
      CObject::Serialize(archive);
 
      // chceme citat alebo zapisovat?
       if(archive.IsStoring())
      {
                  archive << m_strMeno << m_strPriezvisko << m_nRocnik << m_nKredity;
      }
      else
      {
                  archive >> m_strMeno >> m_strPriezvisko >> m_nRocnik >> m_nKredity;
      }
}

Funkcia IsStoring (členská funkcia triedy CArchive) určuje, či chceme stav objektu zapisovať alebo čítať. Operátory << a >> triedy CArchive vždy vracajú referenciu na objekt CArchive, preto je zápis v 

archive >> m_strMeno >> m_strPriezvisko >>...

správny.

Teraz už môžeme pomocou objektu CArchive serializovať objekty tejto triedy. Keďže serializovať znamená ukladať stav objektu, ktorý je určený hodnotami jeho členských premenných, pridajte tieto verejné členské premenné do triedy CStudentSerial:

CString m_strMeno;
CString m_strPriezvisko;
int m_nRocnik;
int m_nKredity;

Pridajte aj dve verejné funkcie (jedna bude slúžiť na zápis a druhá na čítanie dát).

void Read();
void Write();

Do zdrojového súboru pridajte kód funkcie Read:

void CStudentSerial::Read()
{
      char* pszFileName = "c:\\pokus.dat";
     
      // skonstruujeme objekt CFile
      CFile theFile(pszFileName, CFile::modeRead);
      // skonstruujeme objekt CArchive a
      // spojime ho s objektom CFile
      CArchive archive(&theFile, CArchive::load);
      // citame data
      Serialize(archive);
      // zavrieme subor aj objekt CArchive
      archive.Close();
      theFile.Close();
}

A kód funkcie Write:

void CStudentSerial::Write()
{
      char* pszFileName = "c:\\pokus.dat";
     
      CFile theFile(pszFileName, CFile::modeCreate | CFile::modeWrite);
      CArchive archive(&theFile, CArchive::store);
      Serialize(archive);
      archive.Close();
      theFile.Close();
}

Kód v týchto funkciách som z dôvodu šetrenia miesta neošetril na prípadné výnimky ako v príklade 1, ale nič vám nebráni tak urobiť. Pri konštrukcii objektu CArchive určuje druhý parameter konštruktora, či chceme dáta zapisovať alebo čítať.  Nesmieme zabudnúť ani na uzavretie objektov CFileCArchive. Keby ste nevolali funkciu Serialize, mohli by ste zapisovať/čítať dáta priamo vo funkcii Write/Read pomocou preťažených operátorov << a >> triedy CArchive.

Presuňte sa teraz do súboru BasicSerial.cpp, kde pridajte jednu include direktívu:

#include "StudentSerial.h"

Teraz pridajte tento kód do vetvy else funkcie _tmain:  

// vytvorime objekt
CStudentSerial studentObject;
 
// naplnime ho datami
studentObject.m_strMeno="Janko";
studentObject.m_strPriezvisko="Mrkvicka";
studentObject.m_nRocnik=2;
studentObject.m_nKredity=66;
 
// zapiseme data
studentObject.Write();
 
// vyprazdnime premenne
studentObject.m_strMeno="";
studentObject.m_strPriezvisko="";
studentObject.m_nRocnik=0;
studentObject.m_nKredity=0;
 
// precitame data
studentObject.Read();
 
// vypiseme data
cout << (LPCTSTR)studentObject.m_strMeno << "\n";
cout << (LPCTSTR)studentObject.m_strPriezvisko << "\n" ;
cout << (UINT)studentObject.m_nRocnik << "\n";
cout << (UINT)studentObject.m_nKredity << "\n";

A to je všetko. Príklad si vyskúšajte (odporúčam odkrokovať) a skúste zapisovať dáta priamo vo funkciách Read/Write bez použitia funkcie Serialize (ale ako som už spomínal, takýto postup neodporúčam).

Poznámka k operátorom << a >> triedy Carchive. Trieda CArchive má preťažené tieto operátory len pre niektoré dátové typy jazyka C++ a triedy MFC (pozri tab. 2). Pri použití nejakého iného dátového typu je nevyhnutné explicitné pretypovanie na typ, ktorý CArchive pozná. Tu treba dať pozor na to, že operátor zapisovania (<<) je preťažený pre hodnoty a operátor čítania (>>) je preťažený pre referencie. Ak máme napríklad premennú m_nVar typu enum, správny kód, ktorý realizuje zápis/čítanie pomocou CArchive, by vyzeral takto:  

// zapis
archive << (int) m_nVar;
// citanie
archive >> (int&) m_nVar;

 

CObject* (ukazovateľ na CObject)

SIZE and CSize

Float

WORD

CString

POINT a Cpoint

DWORD

BYTE

RECT a Crect

Double

LONG

CTime a CtimeSpan

Int

COleCurrency

ColeVariant

COleDateTime

COleDateTimeSpan

 

Tab. 2 Dátové typy a triedy, ktoré operátory << a >> CArchive priamo poznajú

Problémy s vloženými objektmi a ukazovateľmi. Čo keby mala trieda CStudentSerial v sebe vložené nejaké iné objekty? Ako by potom vyzerala serializácia? Treba rozlišovať dva prípady: Trieda obsahuje vložený ďalší objekt alebo trieda obsahuje len ukazovateľ na ďalší objekt.

Ak chceme tento ďalší objekt (teraz je jedno, či je priamo vložený alebo máme k dispozícii len ukazovateľ) serializovať, musí byť odvodený od CObject, obsahovať funkciu Serialize a serializačné makrá. Ak je objekt priamo vložený do triedy, potom ho serializujeme tak, že vo funkcii Serialize triedy CStudentSerial zavoláme funkciu Serialize tohto objektu. Ak máme k dispozícii len ukazovateľ na tento objekt, môžeme ho serializovať  priamo s dátovými členmi triedy CStudentSerial, pretože operátory CArchive dokážu pracovať s ukazovateľmi na CObject (čo v prvom prípade, keď máme len objekt typu Cobject, nebolo možné). Pre lepšie pochopenie si pozrite nasledujúce výpisy:

Prípad 1: V triede CStudentSerial máme vložený ďalší objekt:

public:
      CRozvrh m_rozvrh;

Trieda CRozvrh obsahuje jeden dátový člen m_strDen typu CString. Má svoju funkciu Serialize. Funkcia Serialize triedy CStudentSerial potom vyzerá takto:     

void CStudentSerial::Serialize(CArchive& archive)
{
    // najprv zavolame povodnu funkciu
      CObject::Serialize(archive);
 
      // serializujeme vlozeny objekt
      m_rozvrh.Serialize(archive);
      ...
}

Prípad 2: V triede CStudentSerial máme len ukazovateľ na ďalší objekt:

public:
CRozvrh* m_rozvrh;

Potom funkcia Serialize vyzerá takto:

void CStudentSerial::Serialize(CArchive& archive)
{
       // najprv zavolame povodnu funkciu
      CObject::Serialize(archive);
 
      // chceme citat alebo zapisovat
      if(archive.IsStoring())
      {
                  // m_rozvrh mozeme serializovat priamo
archive << m_strMeno << m_strPriezvisko << m_nRocnik << m_nKredity << m_rozvrh;
      }
      else
      {
                  archive >> m_strMeno >> m_strPriezvisko >> m_nRocnik >> m_nKredity >> m_rozvrh;
      }
}

 

Tipy na doma. Vyskúšajte si prácu aj s triedami odvodenými od CFile (odporúčam vyskúšať triedu CStdioFile). V helpe si pozrite podrobnejšie informácie o triede CArchive a jej členoch. Ošetrite kód funkcií WriteRead v príklade 2, aby zachytával výnimky, ktoré môžu vzniknúť pri práci s CFileCArchive, a pokúste sa nasimulovať stavy, aby tieto výnimky aj nastali. Overte si na príklade, ako sa serializujú vložené objekty a ukazovatele na objekty.  

Nabudúce. V ďalšej časti si preberieme serializáciu v  aplikáciách SDI a pri tej príležitosti si povieme niečo o triedach CEditView a CRichEditView. Dovidenia v marci.                             

Zobrazit Galériu

Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Ako zbaviť fotky hmly

08.12.2016 11:59

Hmla alebo dym sú často veľmi kreatívne nástroje. No všetkého veľa škodí. Fotka potom stráca kontrast a v podstate na nej nič nevidieť. Hmlu môžete neraz následnými úpravami odstrániť alebo zredukovať ...

Ako na to

Užitočné SW nástroje

08.12.2016 11:53

AllDup v4.0.3 Určenie: program na vyhľadávanie a odstraňovanie duplicitných súborov Vlastnosti: duplicitné súbory sa vyhľadávajú len na zvolených diskových jednotkách alebo len v rámci vybraných ...

Ako na to

Fotografovanie s bleskom

08.12.2016 11:47

Ak máte moderný fotoaparát so vstavaným alebo externým bleskom, zdá sa vám téma článku triviálna. Jednoducho nastavíte vhodný režim, vyberiete najlepšiu kompozíciu záberu, exponujete a o zvyšok sa už ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá