TN002: Formato di dati persistenti oggetto

Questa nota descrive le routine di MFC che supportano gli oggetti C++ persistenti e il formato dei dati oggetto quando esso viene memorizzato in un file. Si applica solo alle classi con la macro DECLARE_SERIAL e IMPLEMENT_SERIAL.

Il problema

L'implementazione MFC per dati persistenti si basa su un formato binario compatto per il salvataggio dei dati per molti oggetti in una sola parte contigua di un file. Questo formato binario fornisce la struttura per quanto i dati vengono archiviati, ma è funzione di membro Serialize dell'oggetto che fornisce i dati effettivi salvati dall'oggetto.

MFC risolve il problema strutturante utilizzando la classe CArchive. Un oggetto CArchive fornisce un contesto per la persistenza che dura dal momento in cui l'archivio viene creato finché non viene chiamata la funzione membro CArchive::Close , in modo esplicito dal programmatore o implicitamente dal distruttore quando è uscito l'ambito contenente il CArchive.

Questa nota viene descritta l'implementazione dei membri CArchive ReadObject e WriteObject. ReadObject e WriteObject non sono chiamati direttamente da vengono invece utilizzate dagli specifici della classe type-safe inserimento ed estrazione operatori generati automaticamente dalle macro DECLARE_SERIAL e IMPLEMENT_SERIAL.

classe CMyObject: CObject pubblica
{
 nbsp;  DECLARE_SERIAL(CmyObject)
};

IMPLEMENT_SERIAL (CMyObj, CObject, 1)

/ / esempio di utilizzo (ar è un oggetto CArchive &)
CMyObject * pObj;
CArchive & ar;
AR << pObj;        / / chiama ar.WriteObject(pObj)
AR >> pObj;        / / chiama ar.ReadObject(RUNTIME_CLASS(CObj))

Questa nota viene descritto il codice che si trova nel file di origine MFC ARCOBJ.CPP. L'implementazione di CArchive principale può essere trovato in ARCCORE.CPP.

Salvataggio di oggetti al negozio (CArchive::WriteObject)

La funzione membro CArchive::WriteObject scrive i dati di intestazione utilizzati per ricostruire l'oggetto. Questi dati si compone di due parti: il tipo dell'oggetto e lo stato dell'oggetto. Questa funzione membro è anche responsabile per il mantenimento dell'identità dell'oggetto sta scritto fuori, così che solo una singola copia viene salvata, indipendentemente dal numero di puntatori a quell'oggetto (compresi i puntatori circolare).

Risparmio (inserimento) e ripristino di oggetti (estrazione) si basa su diversi "costanti manifesto". Questi sono i valori che sono memorizzati in formato binario e forniscono informazioni importanti per l'archivio (si noti il prefisso "w" indica la quantità a 16 bit):

Etichetta Descrizione
wNullTag Utilizzati per i puntatori a oggetti NULL (0).
wNewClassTag Indica la descrizione della classe che segue è nuovo a questo archivio context—(-1).
wOldClassTag Indica la classe dell'oggetto da leggere è stato visto in questo contesto (0x8000).

Durante la memorizzazione di oggetti, l'Archivio conserva un CMapPtrToPtr ( m_pStoreMap) che è un mapping da un oggetto memorizzato su un identificatore persistente a 32-bit (PID). Un PID viene assegnato a ogni oggetto unico e ogni nome di classe unico che viene salvato nel contesto dell'archivio. Questi PID sono distribuiti in sequenza a partire da 1. È importante notare che questi PID non avere alcun significato di fuori dell'archivio e, in particolare, non sono deve essere confuso con il numero di record o altri elementi di identità.

A partire da MFC versione 4.0 la classe CArchive è stato esteso per supportare molto grandi archivi. Nelle versioni precedenti, un PID era una quantità a 16 bit, limitando l'archivio a 0x7FFE (32766) oggetti. Immunodeficienze Primitive sono ora a 32-bit, ma essi sono scritte come 16 bit a meno che siano più grandi di 0x7FFE. PIDs grandi sono scritti come 0x7FFF, seguito dal PID 32-bit. Questa tecnica mantiene la compatibilità con le versioni precedenti dei file.

Quando viene effettuata una richiesta per salvare un oggetto in un archivio (solitamente con l'operatore di inserimento globale), viene effettuato un controllo per un puntatore NULL CObject ; Se il puntatore è NULL, la wNullTag viene inserito nel flusso di archivio.

Se abbiamo un puntatore all'oggetto reale che è capace di essere serializzato (la classe è una classe DECLARE_SERIAL ), abbiamo quindi controllare il m_pStoreMap per vedere se l'oggetto è stato salvato già. Se ha, inseriamo il PID 32-bit associato a quell'oggetto.

Se l'oggetto non è stato salvato prima, ci sono due possibilità, noi dobbiamo tener conto: sia l'oggetto e il tipo esatto (cioè, classe) dell'oggetto sono nuovi di questo contesto Archivio oppure l'oggetto è di un tipo esatto di già visto. Per determinare se il tipo è stato visto in che noi la m_pStoreMap di un oggetto CRuntimeClass che corrisponda all'oggetto CRuntimeClass associato all'oggetto che ci stiamo salvando una query. Se abbiamo visto questa classe prima, WriteObject inserisce un tag che è il bit per bit OR'ing di wOldClassTag e questo indice. Se il CRuntimeClass è nuovo a questo contesto di archivio, quindi WriteObject assegna un nuovo PID a tale classe e inserirlo nel archivio, preceduto dal valore wNewClassTag.

Il descrittore per questa classe viene quindi inserito nell'archivio utilizzando la funzione membro CRuntimeClass Store. CRuntimeClass::Store inserisce il numero dello schema della classe (vedi sotto) e il nome di testo ASCII della classe. Si noti che l'uso del nome del testo ASCII non garantisce univocità dell'archivio tra le applicazioni, quindi è consigliabile per contrassegnare i file di dati per prevenire la corruzione. In seguito all'inserimento delle informazioni classe, l'archivio inserisce l'oggetto nella m_pStoreMap e quindi chiama la funzione membro Serialize per inserire i dati specifici della classe nell'archivio. Immissione dell'oggetto nella m_pStoreMap prima di chiamare Serialize impedisce più copie dell'oggetto di essere salvate nell'archivio.

Quando si torna al chiamante iniziale (di solito la radice della rete degli oggetti), è importante stretta l'archivio. Se altre operazioni CFile stanno per essere fatto, la funzione di membro CArchive Flush deve essere chiamata. In caso contrario si tradurrà in un archivio corrotto.

&Notanbsp;  Questa implementazione impone un limite rigido di indici di 0x3FFFFFFE al contesto di archivio. Questo numero rappresenta il numero massimo di oggetti unici e le classi che possono essere salvate in un unico archivio, ma nota che un singolo disco file può avere un numero illimitato di contesti di archivio.

Caricamento di oggetti dall'archivio (CArchive::ReadObject)

Caricamento di oggetti (estrazione) utilizza la funzione membro CArchive::ReadObject ed è l'inverso di WriteObject. Come con WriteObject, ReadObject non viene chiamato direttamente dal codice utente; codice utente deve chiamare l'operatore indipendente dai tipi estrazione che chiama ReadObject con l' atteso CRuntimeClass. Questo modo si assicura l'integrità del tipo dell'operazione Estratto.

Poiché l'implementazione WriteObject assegnati PIDs crescente, a partire da 1 (0 è predefinita dell'oggetto NULL), l'implementazione ReadObject può utilizzare una matrice per mantenere lo stato del contesto archivio. Quando un PID viene letto dal negozio, se il PID è maggiore il limite superiore corrente della m_pLoadArray, poi ReadObject sa che un nuovo oggetto (o la descrizione della classe) segue.

Numeri dello schema

Il numero dello schema, che viene assegnato alla classe quando IMPLEMENT_SERIAL la classe viene rilevato, è la "versione" di implementazione della classe. Lo schema si riferisce all'implementazione della classe, non al numero di volte in cui un determinato oggetto è stato fatto persistente (chiamata comunemente la versione in oggetto).

Se avete i&ntenzione di mantenere diverse implementazioni diverse della stessa classe nel corso del tempo, con incremento dello schema come rivedere la implementazione di funzione un membro dell'oggetto Serialize vi permetterà di scrivere il codice che può caricare gli oggetti archiviati utilizzando le versioni precedenti della implementation.nbsp;

La funzione membro CArchive::ReadObject genererà un CArchiveException quando incontra un numero dello schema nell'archivio persistente che si differenzia dal numero dello schema della descrizione della classe nella memoria. Non è facile recuperare da questa eccezione.

È possibile utilizzare VERSIONABLE_SCHEMA O ha avuto con la versione dello schema di mantenere questa eccezione venga generata. Utilizzando il VERSIONABLE_SCHEMA, il codice può intraprendere l'azione appropriata nella sua funzione Serialize controllando il valore restituito da CArchive::GetObjectSchema.

Chiamata serializzare direttamente

Ci sono molti casi dove l'overhead del regime generale oggetto archivio di WriteObject e ReadObject non è necessario o desiderato. Questo è il caso del comune di serializzazione dei dati in un CDocument. In questo caso la funzione membro Serialize della CDocument viene chiamato direttamente, non con l'estratto o inserire gli operatori. Il contenuto del documento a sua volta può utilizzare lo schema di archivio più generale oggetto.

Chiamata diretta Serialize ha i seguenti vantaggi e svantaggi:

Poiché Serialize viene chiamato direttamente sul vostro documento, non è generalmente possibile per gli oggetti secondari del documento per archiviare i riferimenti al documento loro padre. Questi oggetti devono essere dato un puntatore al loro documento contenitore in modo esplicito o è necessario utilizzare la funzione CArchive::MapObject per mappare il puntatore CDocument un PID prima che questi puntatori posteriore sono archiviati.

Come notato sopra, si devono codificare informazioni sulla versione e classe te stesso quando si chiama Serialize direttamente, consentendo di modificare il formato più tardi, pur mantenendo la compatibilità con i file più vecchi. La funzione CArchive::SerializeClassRef può essere chiamata in modo esplicito prima della serializzazione di un oggetto direttamente o prima di chiamare una classe base.

&Note tecniche per numero |nbsp; Note tecniche per la categoria

Index