Udostępnij przez


TN002: trwały format 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 .

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 wychodzi z zakresu zawierającego element CArchive.

W tej notatce opisano implementację CArchive elementów członkowskich CArchive::ReadObject i CArchive::WriteObject. Kod dla tych funkcji znajdziesz w Arcobj.cpp i główną implementację w CArchive Arccore.cpp. Kod użytkownika nie wywołuje ReadObject ani WriteObject bezpośrednio. Zamiast tego te obiekty są używane przez klaso-specyficzne operatory bezpieczne typowo do wstawiania i wyodrębniania, 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, które są używane do rekonstrukcji 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 postaci binarnej i zawierają ważne informacje dla archiwum (zwróć uwagę, że prefiks "w" oznacza 16-bitowe wartości).

Etykieta Opis
wNullTag Używany dla wskaźników obiektów NULL (0).
wNewClassTag Wskazuje opis klasy, który jest nowy w kontekście tego 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 przechowywanego obiektu 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 PID są zapisywane jako 0x7FFF, następnie 32-bitowy 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, 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 tego samego typu, który już był widoczny. Aby określić, czy typ został zauważony, kod wysyła zapytanie do m_pStoreMap o obiekt CRuntimeClass, który pasuje do obiektu skojarzonego z zapisywanym obiektem. Jeśli istnieje dopasowanie, WriteObject wstawia tag, który jest wynikiem operacji bitowej na OR i tym indeksie. 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 do sklepu.

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 / Notatka

Ta implementacja nakłada ostateczny 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 repozytorium (CArchive::ReadObject)

Ładowanie (wyodrębnianie) obiektów używa metody CArchive::ReadObject i jest odwrotnością WriteObject. Podobnie jak w przypadku WriteObject, ReadObject nie jest wywoływany bezpośrednio przez kod użytkownika. Kod użytkownika powinien wywołać bezpiecznego typu operatora wyodrębniania, który wywołuje ReadObject z oczekiwaną CRuntimeClass. 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. Gdy PID jest odczytywany z magazynu i jest większy niż bieżąca górna granica m_pLoadArray, wiadomo, że następuje nowy obiekt (lub opis klasy).

Numery schematów

Numer schematu, który jest przypisywany do klasy, gdy napotkano metodę IMPLEMENT_SERIAL 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.

Aby uniemożliwić zgłoszenie tego wyjątku, możesz użyć VERSIONABLE_SCHEMA w połączeniu z wersją schematu przy użyciu operacji bitowej OR. Za pomocą polecenia VERSIONABLE_SCHEMAkod może wykonać odpowiednią akcję w funkcji Serialize , sprawdzając wartość zwracaną z CArchive::GetObjectSchema.

Bezpośrednie wywoływanie serializacji

W wielu przypadkach ogólny schemat archiwum obiektów WriteObject i ReadObject nie jest konieczny. Jest to typowy przypadek serializacji danych do dokumentu CDocument. W tym przypadku metoda Serialize 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 skonfigurowany tak, aby implementacje WriteObject i ReadObject oraz powiązane kolekcje nie były zintegrowane z aplikacją, chyba że potrzebujesz bardziej ogólnego mechanizmu archiwizacji obiektów do innego 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 dowolnych numerów identyfikacyjnych używanych na początku plików danych.

  • Każdy obiekt, który jest serializowany za pomocą bezpośredniego wywołania Serialize, nie powinien używać CArchive::GetObjectSchema lub powinien 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 powinny być przekazane poprzez jawny wskaźnik do dokumentu kontenera lub należy użyć funkcji CArchive::MapObject, aby zamapować wskaźnik na PID przed zarchiwizowaniem tych wskaźników wstecznych.

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 także

Uwagi techniczne według numeru
Uwagi techniczne według kategorii