Udostępnij za pośrednictwem


TN002: format trwałych danych obiektu

W tej notatce opisano procedury MFC, które obsługują trwałe obiekty języka C++ oraz format danych obiektu, gdy są przechowywane w pliku. Dotyczy to tylko klas z makrami DECLARE_SERIAL i IMPLEMENT_SERIAL .

The Problem

Implementacja MFC dla trwałych danych przechowuje dane dla wielu obiektów w pojedynczej ciągłej części pliku. Metoda obiektu Serialize tłumaczy dane obiektu na kompaktowy format binarny.

Implementacja gwarantuje, że wszystkie dane są zapisywane w tym samym formacie przy użyciu klasy CArchive. Używa CArchive obiektu jako tłumacza. Ten obiekt będzie się powtarzać od momentu jego utworzenia, dopóki nie wywołasz metody CArchive::Close. Tę metodę można wywołać jawnie przez programistę lub niejawnie przez destruktor, gdy program zamyka zakres zawierający CArchiveelement .

W tej notatce opisano implementację CArchive elementów członkowskich CArchive::ReadObject i CArchive::WriteObject. Kod dla tych funkcji można znaleźć w pliku Arcobj.cpp oraz główną implementację pliku CArchive w pliku Arccore.cpp. Kod użytkownika nie wywołuje ReadObject ani WriteObject bezpośrednio. Zamiast tego te obiekty są używane przez operatory wstawiania i wyodrębniania specyficzne dla klasy, które są generowane automatycznie przez makra DECLARE_SERIAL i IMPLEMENT_SERIAL. Poniższy kod pokazuje, jak WriteObject i ReadObject są niejawnie wywoływane:

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))

Zapisywanie obiektów w magazynie (CArchive::WriteObject)

Metoda CArchive::WriteObject zapisuje dane nagłówka używane do odtworzenia obiektu. Te dane składają się z dwóch części: typu obiektu i stanu obiektu. Ta metoda jest również odpowiedzialna za utrzymanie tożsamości zapisywanego obiektu, dzięki czemu tylko jedna kopia jest zapisywana, niezależnie od liczby wskaźników do tego obiektu (w tym wskaźników cyklicznych).

Zapisywanie (wstawianie) i przywracanie (wyodrębnianie) obiektów opiera się na kilku "stałych manifestu". Są to wartości przechowywane w pliku binarnym i zawierają ważne informacje do archiwum (zwróć uwagę, że prefiks "w" wskazuje 16-bitowe ilości):

Tag opis
wNullTag Używany dla wskaźników obiektów NULL (0).
wNewClassTag Wskazuje opis klasy, który jest nowy dla tego kontekstu archiwum (-1).
wOldClassTag Wskazuje, że klasa odczytywanego obiektu była widoczna w tym kontekście (0x8000).

Podczas przechowywania obiektów archiwum utrzymuje obiekt CMapPtrToPtr(m_pStoreMap), który jest mapowaniem z obiektu przechowywanego na 32-bitowy identyfikator trwały (PID). Identyfikator PID jest przypisywany do każdego unikatowego obiektu i każdej unikatowej nazwy klasy zapisanej w kontekście archiwum. Te identyfikatory PID są przekazywane sekwencyjnie począwszy od 1. Te identyfikatory PID nie mają znaczenia poza zakresem archiwum, a w szczególności nie należy mylić ich z liczbami rekordów ani innymi elementami tożsamości.

CArchive W klasie identyfikatory PID są 32-bitowe, ale są zapisywane jako 16-bitowe, chyba że są większe niż 0x7FFE. Duże identyfikatory PID są zapisywane jako 0x7FFF a następnie 32-bitowe identyfikatory PID. Zapewnia to zgodność z projektami utworzonymi we wcześniejszych wersjach.

Po wysłaniu żądania zapisania obiektu w archiwum (zwykle przy użyciu operatora wstawiania globalnego) jest wykonywane sprawdzanie wskaźnika CObject o wartości NULL. Jeśli wskaźnik ma wartość NULL, element wNullTag zostanie wstawiony do strumienia archiwum.

Jeśli wskaźnik nie ma wartości NULL i może być serializowany (klasa jest klasą DECLARE_SERIAL ), kod sprawdza m_pStoreMap , aby sprawdzić, czy obiekt został już zapisany. Jeśli tak, kod wstawia 32-bitowy identyfikator PID skojarzony z tym obiektem do strumienia archiwum.

Jeśli obiekt nie został wcześniej zapisany, istnieją dwie możliwości do rozważenia: zarówno obiekt, jak i dokładny typ (czyli klasa) obiektu są nowe w tym kontekście archiwum lub obiekt jest dokładnie widoczny. Aby określić, czy typ został zaobserwowany, kod wysyła zapytanie do m_pStoreMap dla obiektu CRuntimeClass , który pasuje CRuntimeClass do obiektu skojarzonego z zapisywanym obiektem. Jeśli istnieje dopasowanie, WriteObject wstawia tag, który jest bitem OR elementu wOldClassTag i tego indeksu. Jeśli element CRuntimeClass jest nowy w tym kontekście archiwum, WriteObject przypisuje nową nazwę PID do tej klasy i wstawia ją do archiwum, poprzedzoną wartością wNewClassTag .

Deskryptor dla tej klasy jest następnie wstawiany do archiwum przy użyciu CRuntimeClass::Store metody . CRuntimeClass::Store Wstawia numer schematu klasy (patrz poniżej) i nazwę tekstu ASCII klasy. Należy pamiętać, że użycie nazwy tekstu ASCII nie gwarantuje unikatowości archiwum w aplikacjach. W związku z tym należy otagować pliki danych, aby zapobiec uszkodzeniu. Po wstawieniu informacji o klasie archiwum umieszcza obiekt w m_pStoreMap, a następnie wywołuje metodę Serialize w celu wstawienia danych specyficznych dla klasy. Umieszczenie obiektu w m_pStoreMap przed wywołaniem Serialize uniemożliwia zapisanie wielu kopii obiektu w magazynie.

Podczas powrotu do początkowego obiektu wywołującego (zazwyczaj katalogu głównego sieci obiektów) należy wywołać metodę CArchive::Close. Jeśli planujesz wykonać inne operacje CFile, musisz wywołać metodę CArchiveFlush , aby zapobiec uszkodzeniu archiwum.

Uwaga

Ta implementacja nakłada sztywny limit 0x3FFFFFFE indeksów na kontekst archiwum. Ta liczba reprezentuje maksymalną liczbę unikatowych obiektów i klas, które można zapisać w jednym archiwum, ale pojedynczy plik dysku może mieć nieograniczoną liczbę kontekstów archiwum.

Ładowanie obiektów z magazynu (CArchive::ReadObject)

Ładowanie (wyodrębnianie) obiektów używa CArchive::ReadObject metody i jest odwrotnym elementem WriteObject. Podobnie jak w przypadku WriteObjectelementu , ReadObject nie jest wywoływany bezpośrednio przez kod użytkownika. Kod użytkownika powinien wywołać operatora wyodrębniania bezpiecznego typu, który wywołuje ReadObject metodę z oczekiwaną CRuntimeClasswartością . Zapewnia to integralność typu operacji wyodrębniania.

Ponieważ implementacja WriteObject przypisano rosnące identyfikatory PID, począwszy od 1 (0 jest wstępnie zdefiniowany jako obiekt NULL), implementacja ReadObject może używać tablicy do zachowania stanu kontekstu archiwum. Jeśli piD jest odczytywany ze sklepu, jeśli piD jest większy niż bieżąca górna granica m_pLoadArray, wie, ReadObject że następuje nowy obiekt (lub opis klasy).

Numery schematów

Numer schematu, który jest przypisywany do klasy po IMPLEMENT_SERIAL napotkaniu metody klasy, jest "wersją" implementacji klasy. Schemat odnosi się do implementacji klasy, a nie do liczby przypadków, gdy dany obiekt został wykonany jako trwały (zwykle określany jako wersja obiektu).

Jeśli zamierzasz zachować kilka różnych implementacji tej samej klasy w czasie, zwiększanie schematu podczas poprawiania implementacji metody obiektu Serialize umożliwi napisanie kodu, który może ładować obiekty przechowywane przy użyciu starszych wersji implementacji.

Metoda CArchive::ReadObject zgłosi wyjątek CArchiveException , gdy napotka numer schematu w magazynie trwałym, który różni się od numeru schematu opisu klasy w pamięci. Odzyskanie z tego wyjątku nie jest łatwe.

Możesz użyć VERSIONABLE_SCHEMA połączenia z wersją schematu (bitowej OR), aby uniemożliwić zgłaszanie tego wyjątku. Za pomocą polecenia VERSIONABLE_SCHEMAkod może wykonać odpowiednią akcję w funkcji Serialize , sprawdzając wartość zwracaną z CArchive::GetObjectSchema.

Bezpośrednie wywoływanie serializowania

W wielu przypadkach obciążenie ogólnego schematu WriteObject archiwum obiektów i ReadObject nie jest konieczne. Jest to typowy przypadek serializacji danych do dokumentu CDocument. W tym przypadku Serialize metoda CDocument metody jest wywoływana bezpośrednio, a nie za pomocą operatorów wyodrębniania lub wstawiania. Zawartość dokumentu może z kolei korzystać z bardziej ogólnego schematu archiwum obiektów.

Wywołanie Serialize bezpośrednio ma następujące zalety i wady:

  • Żadne dodatkowe bajty nie są dodawane do archiwum przed lub po serializacji obiektu. To nie tylko sprawia, że zapisane dane są mniejsze, ale umożliwia zaimplementowanie Serialize procedur, które mogą obsługiwać dowolne formaty plików.

  • MFC jest dostrojony, więc WriteObject implementacje i ReadObject i powiązane kolekcje nie będą połączone z aplikacją, chyba że potrzebujesz bardziej ogólnego schematu archiwum obiektów w innym celu.

  • Kod nie musi odzyskiwać danych ze starych numerów schematów. Dzięki temu kod serializacji dokumentu jest odpowiedzialny za kodowanie numerów schematów, numerów wersji formatu pliku lub niezależnie od numerów identyfikacyjnych używanych na początku plików danych.

  • Każdy obiekt, który jest serializowany za pomocą bezpośredniego wywołania, nie może używać SerializeCArchive::GetObjectSchema lub musi obsługiwać wartość zwracaną (UINT)-1 wskazującą, że wersja była nieznana.

Ponieważ Serialize jest wywoływany bezpośrednio w dokumencie, zwykle nie jest możliwe, aby pod-obiekty dokumentu zarchiwizowały odwołania do dokumentu nadrzędnego. Te obiekty muszą mieć jawny wskaźnik do dokumentu kontenera lub należy użyć funkcji CArchive::MapObject , aby zamapować CDocument wskaźnik na piD przed zarchiwizowanym wskaźnikiem wstecznym.

Jak wspomniano wcześniej, należy zakodować informacje o wersji i klasie podczas bezpośredniego wywoływania Serialize , umożliwiając zmianę formatu później przy zachowaniu zgodności z poprzednimi plikami. Funkcję CArchive::SerializeClass można wywołać jawnie przed bezpośrednią serializacji obiektu lub przed wywołaniem klasy bazowej.

Zobacz też

Uwagi techniczne według numerów
Uwagi techniczne według kategorii