TN002: Formato de dados do objeto persistente
Esta anotação descreve as rotinas MFC que oferecem suporte a objetos persistentes do C++ e o formato dos dados do objeto quando ele é armazenado em um arquivo.Isso se aplica apenas a classes com o DECLARE_SERIAL e IMPLEMENT_SERIAL macros.
O problema
A implementação do MFC para dados persistentes armazena dados de vários objetos em uma única parte contígua de um arquivo.O objeto Serialize método converte os dados do objeto em um formato binário compacto.
A implementação garante que todos os dados são salvos no mesmo formato usando o Classe CArchive.Ele usa um CArchive o objeto como um tradutor.Esse objeto persiste do momento em que ela é criada até que você chamar CArchive::Close.Esse método pode ser chamado explicitamente pelo programador ou implicitamente pelo destruidor quando o programa sai do escopo que contém o CArchive.
Esta nota descreve a implementação de CArchive os membros CArchive::ReadObject e CArchive::WriteObject.Você encontrará o código para essas funções no Arcobj.cpp e a implementação principal para CArchive em Arccore.cpp.Código do usuário não chama ReadObject e WriteObject diretamente.Em vez disso, esses objetos são usados por específicas de classe tipo seguro a inserção e a extração operadores que são geradas automaticamente pelo DECLARE_SERIAL e IMPLEMENT_SERIAL macros.O seguinte código mostra como WriteObject e ReadObject implicitamente são chamados:
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))
Salvando objetos para o armazenamento (CArchive::WriteObject)
O método CArchive::WriteObject grava dados de cabeçalho são usados para reconstruir o objeto.Dados consistem em duas partes: o tipo do objeto e o estado do objeto.Esse método também é responsável por manter a identidade do objeto que está sendo escrito, para que apenas uma única cópia é salva, independentemente do número de ponteiros para o objeto (incluindo ponteiros circulares).
Salvando (Inserir) e restauração de objetos (Extrair) depende de vários "constantes manifesto". Estes são valores que são armazenados em binário e fornecem informações importantes para o arquivamento (Observe o prefixo "w" indica a quantidade de 16 bits):
Marca |
Descrição |
---|---|
wNullTag |
Usado para ponteiros de objeto nula (0). |
wNewClassTag |
Indica a descrição da classe que segue é nova para este contexto de arquivo morto (-1). |
wOldClassTag |
Indica a classe do objeto sendo lido foi observado neste contexto (0x8000). |
Quando o armazenamento de objetos de arquivamento mantém um CMapPtrToPtr (o m_pStoreMap) que é um mapeamento de um objeto armazenado para um identificador persistente de 32 bits (PID).Um PID é atribuído a cada objeto exclusivo e cada nome de classe exclusivo é salvo no contexto de arquivamento.Esses PIDs são distribuídos em seqüência começando em 1.Esses PIDs não têm significado fora do escopo do arquivo e, em particular, não são deve ser confundida com números de registro ou outros itens de identidade.
No CArchive classe, PIDs são 32 bits, mas eles são gravados como 16 bits a menos que sejam maiores 0x7FFE.PIDs grandes são gravados como 0x7FFF seguido o PID de 32 bits.Isso mantém compatibilidade com projetos criados em versões anteriores.
Quando é feita uma solicitação para salvar um objeto em um arquivo (normalmente usando o operador de inserção global), uma verificação é feita para um valor nulo CObject ponteiro.Se o ponteiro for NULL, o wNullTag é inserido no fluxo de arquivo morto.
Se o ponteiro não for NULL e pode ser serializado (a classe é uma DECLARE_SERIAL classe), o código verifica o m_pStoreMap para ver se o objeto já foi salvo.Se tiver, o código insere o PID de 32 bits associado ao fluxo de arquivo morto.
Se o objeto não tiver sido salvo antes, há duas possibilidades a serem considerados: o objeto e o tipo exato (isto é, classe) do objeto são novos para este contexto de arquivo ou o objeto é de um tipo exato já visto.Para determinar se o tipo foi observado, as consultas de código o m_pStoreMap para um CRuntimeClass objeto corresponde a CRuntimeClass objeto associado com o objeto sendo salvo.Se houver uma correspondência, WriteObject insere uma marca que é o bit a bit OR de wOldClassTag e esse índice.Se o CRuntimeClass é um novo contexto de arquivo, WriteObject atribui um novo PID dessa classe e insere o arquivamento, precedido de wNewClassTag valor.
O descritor para essa classe é inserido no arquivamento usando o CRuntimeClass::Store método.CRuntimeClass::StoreInsere o número de esquema de classe (veja abaixo) e o nome de texto ASCII da classe.Observe que o uso do nome de texto ASCII não garante a exclusividade do arquivamento entre aplicativos.Portanto, você deve marcar os arquivos de dados para evitar a corrupção.Após a inserção de informações de classe, o arquivamento coloca o objeto de m_pStoreMap e chama o Serialize método para inserir dados específicos de classe.Colocando o objeto de m_pStoreMap antes de chamar Serialize impede que várias cópias do objeto sendo salvos no armazenamento.
Ao retornar para o chamador inicial (geralmente a raiz da rede de objetos), você deve chamar CArchive::Close.Se você planeja realizar outras CFileoperações, você deve chamar o CArchive método liberar para evitar a corrupção do arquivamento.
Observação |
---|
Essa implementação impõe um limite rígido de 0x3FFFFFFE índices por contexto de arquivamento.Esse número representa o número máximo de objetos exclusivos e classes que podem ser salvos em um único arquivo, mas um arquivo único disco pode ter um número ilimitado de contextos de arquivamento. |
Carregando objetos do armazenamento (CArchive::ReadObject)
Carregar (Extrair) usa objetos de CArchive::ReadObject método e é o contrário de WriteObject.Como com WriteObject, ReadObject não é chamado diretamente pelo código do usuário; código do usuário deve chamar o operador de extração de tipo seguro que chama ReadObject com o esperado CRuntimeClass.Isso assegura a integridade do tipo de operação de extração.
Desde que o WriteObject implementação atribuído PIDs crescentes, começando com 1 (0 predefinido do objeto nulo), o ReadObject implementação pode usar uma matriz para manter o estado do contexto de arquivamento.Quando um PID é lido do armazenamento, se o PID for maior que o atual limite superior da m_pLoadArray, ReadObject sabe que segue um novo objeto (ou a descrição de classe).
Números de esquema
O número de esquema, que é atribuído à classe quando o IMPLEMENT_SERIAL método da classe é encontrado, é a "versão" da implementação de classe.O esquema refere-se a implementação da classe, não para o número de vezes que um determinado objeto foi feito persistente (geralmente conhecido como a versão do objeto).
Se você pretende manter várias implementações diferentes da mesma classe ao longo do tempo, incrementando o esquema como revisar seu objeto Serialize implementação do método permitem que você escrever código que pode carregar objetos armazenados usando versões mais antigas da implementação.
O CArchive::ReadObject método lançará um CArchiveException quando encontra um número de esquema no armazenamento persistente que difere do número de esquema de descrição de classe na memória.Não é fácil recuperar dessa exceção.
Você pode usar VERSIONABLE_SCHEMA combinado com (bit a bit OR) a versão de esquema para manter essa exceção de ser lançada.Usando VERSIONABLE_SCHEMA, seu código pode tomar a ação apropriada seu Serialize função, verificando o valor de retorno de CArchive::GetObjectSchema.
Chamada serializar diretamente
Em muitos casos, a sobrecarga do esquema de arquivamento de objeto geral de WriteObject e ReadObject não é necessário.Este é o caso comum de serialização de dados em um CDocument.Nesse caso, o Serialize método de CDocument é chamado diretamente, não com os operadores de extrair ou inserir.O conteúdo do documento pode usar o esquema de arquivo de objeto mais geral.
Chamando Serialize diretamente tem vantagens e desvantagens a seguir:
Nenhum bytes extras são adicionadas para o arquivamento antes ou após o objeto é serializado.Isso não só torna os dados salvos menores, mas permite que você implemente Serialize rotinas que podem lidar com quaisquer formatos de arquivo.
O MFC é ajustado para que as WriteObject e ReadObject implementações e coleções relacionadas não estarão vinculadas em seu aplicativo, a menos que você precisa o esquema de arquivo de objeto mais geral para algum outro fim.
Seu código não precisa recuperar números de esquema antigas.Isso torna seu código de serialização do documento responsável por números de esquema de codificação, números de versão do formato de arquivo ou qualquer identificação números você usar no início de seus arquivos de dados.
Qualquer objeto é serializado com uma chamada direta para Serialize não deve usar CArchive::GetObjectSchema ou deve identificador de um valor de retorno (UINT) -1 indicando que a versão era desconhecida.
Porque Serialize é chamado diretamente no documento, não é geralmente possível para subobjetos do documento para arquivar as referências ao documento pai.Esses objetos devem ser dada um ponteiro para o documento recipiente explicitamente ou você deve usar CArchive::MapObject função para mapear o CDocument ponteiro para um PID antes desses ponteiros back são arquivados.
Como observado anteriormente, você deve codificar a versão e informações de classe mesmo quando você chamar Serialize diretamente, permitindo que você alterar o formato posteriormente e ainda manter compatibilidade com os arquivos mais antigos.O CArchive::SerializeClass função pode ser chamada explicitamente antes de serializar diretamente um objeto ou antes de chamar uma classe base.