Sdílet prostřednictvím


TN002: Formát dat trvalých objektů

Tato poznámka popisuje rutiny MFC, které podporují trvalé objekty C++ a formát dat objektu, když jsou uloženy v souboru. To platí jenom pro třídy s makry DECLARE_SERIAL a IMPLEMENT_SERIAL .

Problém

Implementace MFC pro trvalá data ukládá data pro mnoho objektů v jedné souvislé části souboru. Metoda objektu Serialize přeloží data objektu do kompaktního binárního formátu.

Implementace zaručuje, že všechna data jsou uložena ve stejném formátu pomocí CArchive Třída. Používá CArchive objekt jako překladatel. Tento objekt přetrvává od okamžiku vytvoření, dokud nevoláte CArchive::Close. Tato metoda může být volána buď explicitně programátorem, nebo implicitně destruktoru, když program ukončí obor, který obsahuje CArchive.

Tato poznámka popisuje implementaci CArchive členů CArchive::ReadObject a CArchive::WriteObject. Kód pro tyto funkce najdete v Arcobj.cpp a hlavní implementaci pro CArchive Arccore.cpp. Uživatelský kód nevolá ReadObject a WriteObject přímo. Místo toho tyto objekty používají operátory vložení a extrakce specifické pro konkrétní třídu, které jsou automaticky generovány DECLARE_SERIAL a IMPLEMENT_SERIAL makry. Následující kód ukazuje, jak WriteObject a ReadObject jsou implicitně volána:

class CMyObject : public CObject
{
    DECLARE_SERIAL(CMyObject)
};

IMPLEMENT_SERIAL(CMyObj, CObject, 1)

// example usage (ar is a CArchive&)
CMyObject* pObj;
CArchive& ar;
ar <<pObj;        // calls ar.WriteObject(pObj)
ar>> pObj;        // calls ar.ReadObject(RUNTIME_CLASS(CObj))

Ukládání objektů do úložiště (CArchive::WriteObject)

Metoda CArchive::WriteObject zapisuje data hlaviček, která se používají k rekonstrukci objektu. Tato data se skládají ze dvou částí: typu objektu a stavu objektu. Tato metoda je také zodpovědná za udržování identity objektu, který se zapisuje, takže se uloží pouze jedna kopie bez ohledu na počet ukazatelů na tento objekt (včetně cyklických ukazatelů).

Ukládání (vkládání) a obnovení (extrahování) objektů závisí na několika "konstantách manifestu". Jedná se o hodnoty, které jsou uloženy v binárním souboru a poskytují důležité informace archivu (všimněte si, že předpona "w" označuje 16bitové množství):

Značka (tag) Popis
wNullTag Používá se pro ukazatele na objekt NULL (0).
wNewClassTag Označuje popis třídy, který následuje, je pro tento archivní kontext novinkou (-1).
wOldClassTag Označuje třídu objektu, který se čte, byl zobrazen v tomto kontextu (0x8000).

Při ukládání objektů archiv udržuje CMapPtrToPtr ( m_pStoreMap), což je mapování z uloženého objektu na 32bitový trvalý identifikátor (PID). Kód PID se přiřadí každému jedinečnému objektu a každému jedinečnému názvu třídy, který je uložen v kontextu archivu. Tyto PID se předávají postupně od 1. Tyto IDENTIFIKÁTORy PID nemají žádný význam mimo rozsah archivu a zejména nejsou zaměňovány s čísly záznamů nebo jinými položkami identity.

CArchive Ve třídě jsou IDENTIFIKÁTORy PID 32bitové, ale jsou napsané jako 16bitové, pokud nejsou větší než 0x7FFE. Velké PID se zapisují jako 0x7FFF následované 32bitovým PID. To udržuje kompatibilitu s projekty vytvořenými v dřívějších verzích.

Při požadavku na uložení objektu do archivu (obvykle pomocí globálního operátoru vložení) se provede kontrola ukazatele NULL CObject . Pokud je ukazatel NULL, wNullTag se vloží do archivu datového proudu.

Pokud ukazatel není NULL a lze serializovat (třída je DECLARE_SERIAL třída), kód zkontroluje m_pStoreMap zjistit, zda byl objekt již uložen. Pokud ano, kód vloží 32bitové PID přidružené k danému objektu do archivovaného datového proudu.

Pokud objekt ještě nebyl uložen, existují dvě možnosti, které je třeba vzít v úvahu: objekt i přesný typ (tj. třída) objektu jsou pro tento archivní kontext novinkou, nebo objekt je přesně vidět. Chcete-li zjistit, zda byl typ zobrazen, kód dotazuje m_pStoreMap pro objekt CRuntimeClass , který odpovídá objektu CRuntimeClass přidruženému k uloženému objektu. Pokud existuje shoda, vloží značku, WriteObject která je bitová OR hodnota wOldClassTag a tohoto indexu. Pokud je pro tento kontext archivu CRuntimeClass novinka, WriteObject přiřadí této třídě nový KÓD PID a vloží ho do archivu před hodnotou wNewClassTag .

Popisovač pro tuto třídu se pak vloží do archivu CRuntimeClass::Store pomocí metody. CRuntimeClass::Store vloží číslo schématu třídy (viz níže) a textový název ASCII třídy. Všimněte si, že použití textového názvu ASCII nezaručuje jedinečnost archivu napříč aplikacemi. Proto byste měli datové soubory označit, abyste zabránili poškození. Po vložení informací o třídě archiv umístí objekt do m_pStoreMap a potom zavolá metodu Serialize pro vložení dat specifických pro třídu. Umístění objektu do m_pStoreMap před voláním Serialize zabrání uložení více kopií objektu do úložiště.

Při návratu do počátečního volajícího (obvykle kořen sítě objektů), musíte volat CArchive::Close. Pokud plánujete provádět další operace CFile, musíte volat metodu CArchive Flush , aby se zabránilo poškození archivu.

Poznámka

Tato implementace představuje pevný limit indexů 0x3FFFFFFE na kontext archivu. Toto číslo představuje maximální počet jedinečných objektů a tříd, které lze uložit v jednom archivu, ale jeden soubor disku může mít neomezený počet kontextů archivu.

Načítání objektů z úložiště (CArchive::ReadObject)

Načítání (extrahování) objektů používá metodu CArchive::ReadObject a je naopak WriteObject. Stejně jako v WriteObjectpřípadě , ReadObject není volána přímo uživatelským kódem; uživatelský kód by měl volat operátor extrakce typu bezpečné, který volá ReadObject s očekávaným CRuntimeClass. Tím se ujišťuje integrita typu operace extrakce.

Vzhledem k tomu, že WriteObject implementace přiřazená rostoucí identifikátory PIN, počínaje hodnotou 1 (0 je předdefinována jako objekt NULL), ReadObject může implementace použít pole k zachování stavu kontextu archivu. Při čtení PID z úložiště, pokud je PID větší než aktuální horní mez m_pLoadArray, ví, ReadObject že nový objekt (nebo popis třídy) následuje.

Čísla schématu

Číslo schématu, které je přiřazeno ke třídě při výskytu IMPLEMENT_SERIAL metody třídy, je "verze" implementace třídy. Schéma odkazuje na implementaci třídy, nikoli na kolikrát byl daný objekt trvalý (obvykle označovaný jako verze objektu).

Pokud máte v úmyslu udržovat několik různých implementací stejné třídy v průběhu času, zvýšení schématu při revizi implementace metody objektu Serialize vám umožní napsat kód, který může načíst objekty uložené pomocí starších verzí implementace.

Metoda CArchive::ReadObject vyvolá CArchiveException , když narazí na číslo schématu v trvalém úložišti, které se liší od čísla schématu popisu třídy v paměti. Z této výjimky není snadné provést obnovení.

Tuto výjimku můžete zachovat VERSIONABLE_SCHEMA v kombinaci s verzí schématu (bitwise OR), abyste tuto výjimku zabránili vyvolání. Pomocí , VERSIONABLE_SCHEMAváš kód může provést příslušnou akci ve své Serialize funkci tak, že zkontroluje návratovou hodnotu z CArchive::GetObjectSchema.

Přímé volání serializace

V mnoha případech režie obecného schématu WriteObject archivu objektů a ReadObject není nutná. Toto je běžný případ serializace dat do CDocument. V tomto případě Serialize je volána metoda CDocument přímo, nikoli s operátory extrakce nebo vložení. Obsah dokumentu může zase používat obecnější schéma archivace objektů.

Přímé volání Serialize má následující výhody a nevýhody:

  • Před nebo po serializaci objektu nejsou do archivu přidány žádné další bajty. Díky tomu jsou uložená data nejen menší, ale umožňuje implementovat Serialize rutiny, které mohou zpracovávat libovolné formáty souborů.

  • Mfc je vyladěný, takže WriteObject implementace a ReadObject související kolekce nebudou propojeny do vaší aplikace, pokud nepotřebujete obecnější schéma archivu objektů pro nějaký jiný účel.

  • Váš kód se nemusí obnovovat ze starých čísel schémat. Díky tomu je kód serializace dokumentu zodpovědný za kódování čísel schématu, čísla verzí formátu souboru nebo jakékoli identifikační čísla, která používáte na začátku datových souborů.

  • Jakýkoli objekt serializovaný s přímým voláním Serialize nesmí použít CArchive::GetObjectSchema nebo musí zpracovat návratovou hodnotu (UINT)-1 označující, že verze byla neznámá.

Vzhledem k tomu Serialize , že je volána přímo v dokumentu, není obvykle možné, aby dílčí objekty dokumentu archivovaly odkazy na jejich nadřazený dokument. Tyto objekty musí být předány ukazatel na dokument kontejneru explicitně nebo musíte použít funkci CArchive::MapObject k namapování CDocument ukazatele na PID před archivací těchto zpětných ukazatelů.

Jak už bylo zmíněno dříve, měli byste při přímém volání Serialize zakódovat informace o verzi a třídě, abyste mohli později změnit formát a zachovat zpětnou kompatibilitu se staršími soubory. Funkci CArchive::SerializeClass lze volat explicitně před přímo serializací objektu nebo před voláním základní třídy.

Viz také

Technické poznámky podle čísel
Technické poznámky podle kategorií