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 WriteObject
pří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_SCHEMA
váš 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 aReadObject
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žítCArchive::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í