TN002: Formato de dados objeto persistente
Esta nota descreve as rotinas MFC que oferecem suporte a objetos persistentes de 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 para vários objetos em uma única parte contígua de um arquivo.Serialize método converte os dados do objeto em um formato binário compacto.
A implementação garante que todos os dados são salvas no mesmo formato, usando o Classe CArchive. Ele usa um CArchive objeto sistema autônomo um tradutor. Este objeto persiste desde o momento em que ele é criado até telefonar CArchive::fechar. Esse método pode ser chamado explicitamente pelo programador ou implicitamente, o destruidor quando o programa sai do escopo que contém o CArchive.
Esta nota descreve a implementação do CArchive membros CArchive::ReadObject e CArchive::WriteObject. Você encontrará o código para essas funções em 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 fortemente tipado inserção e a extração de 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 os dados de cabeçalho que são usados para reconstruir o objeto. Esses dados consistem em duas partes: o tipo de 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 cópia é salvo, independentemente do número de ponteiros para o objeto (inclusive os ponteiros circulares).
Salvar (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 arquivar (Observe o prefixo "w" indica as quantidades de 16 bit):
Marca |
Descrição |
---|---|
wNullTag |
Usado para ponteiros de objeto nula (0). |
wNewClassTag |
Indica a descrição da classe que se segue é novo para este contexto de arquivar (-1). |
wOldClassTag |
Indica a classe do objeto que está sendo lido foi observado neste contexto (0 x 8000). |
Ao armazenar objetos, o arquivar mantém um CMapPtrToPtr (the m_pStoreMap) que é um mapeamento de um objeto armazenado para um identificador persistente 32 bit (PID). Um PID é atribuído a cada objeto exclusivo e cada nome de classe exclusivo que é salva no contexto de arquivar.Esses PIDs são distribuídos em seqüência começando em 1.Esses PIDs não têm nenhum significado fora do escopo do arquivar 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 de 32 bit, mas eles são gravados sistema autônomo 16 bit, a menos que sejam maiores que 0x7FFE. PIDs grandes são gravados sistema autônomo 0x7FFF seguido o PID de 32 bit.Isso mantém compatibilidade com projetos herdado.
Quando é feita uma solicitação para salvar um objeto para um arquivar (normalmente usando o operador global inserção), uma verificação é feita para um nulo CObject ponteiro.Se o ponteiro for nulo, o wNullTag é inserido no fluxo de arquivar.
Se o ponteiro não é nulo e podem ser serializado (a classe é um DECLARE_SERIAL classe), o código verifica o m_pStoreMap Para ver se o objeto já tiver sido salvo. Em caso afirmativo, o código insere o PID de 32 bit associado ao fluxo de arquivar.
Se o objeto não tiver sido salvo antes, há duas possibilidades a serem considerados: tanto o objeto e o tipo exato (isto é, de classe) do objeto são novos para este contexto de arquivar ou o objeto tem um tipo exato já visto.Para determinar se o tipo já foi visto, o código consulta o m_pStoreMap para um CRuntimeClass objeto de 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 nesse contexto de arquivar, WriteObject atribui um novo PID a essa classe e o insere no arquivar, precedido de wNewClassTag valor.
O descritor para essa classe é inserido no arquivar usando o CRuntimeClass::Store método. CRuntimeClass::Store Insere o número do esquema da 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 arquivar 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 arquivar coloca o objeto no m_pStoreMap e, em seguida, chama o Serialize método para inserir dados específicos de classe. Colocando o objeto para o m_pStoreMap antes de ligar Serialize impede que várias cópias do objeto sendo salvo no armazenamento.
Ao retornar para a inicial telefonar er (geralmente a raiz da rede de objetos), você deve telefonar CArchive::fechar. Se você planeja executar outros CFileoperações, você deve telefonar o CArchive método Liberar para evitar a corrupção do arquivar.
Observação: |
---|
Essa implementação impõe um limite rígido de 0x3FFFFFFE índices por contexto de arquivar.Esse número representa o número máximo de objetos exclusivos e classes que podem ser salvas em um único arquivar, mas um arquivar único disco pode ter um número ilimitado de contextos de arquivar. |
Carregando objetos do armazenamento (CArchive::ReadObject)
Carregando objetos (Extrair) usa o CArchive::ReadObject método e é o inverso de WriteObject. sistema autônomo ocorre com WriteObject, ReadObject não é chamado diretamente pelo código do usuário; o código do usuário deve chamar o operador de extração de fortemente tipado que chamadas ReadObject com o esperado CRuntimeClass. Isso assegura a integridade do tipo da operação de extração.
Desde 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 arquivar. Quando um PID é lido a partir do armazenamento, o PID é maior do que o corrente limite superior do m_pLoadArray, ReadObject sabe-se de que segue um novo objeto (ou descrição de classe).
Números de esquema
O número de esquema, que é atribuído à classe quando o IMPLEMENT_SERIALmétodo da classe for encontrado, é a "versão" a implementação da classe. O esquema refere-se a implementação da classe, não para o número de vezes que um determinado objeto tiver sido feito persistente (geralmente conhecido sistema autônomo a versão do objeto).
Se você pretende manter várias implementações diferentes da mesma classe ao longo do time, incrementando o esquema sistema autônomo revisar Serialize implementação do método permitirá que você escrever código que pode carregar objetos armazenados usando versões mais antigas da implementação.
The CArchive::ReadObject método lançará um CArchiveException quando encontrar um número de esquema no armazenamento persistente que seja diferente do número de esquema da descrição da classe na memória.Não é fácil recuperar essa exceção.
Você pode usar VERSIONABLE_SCHEMA combinado com (operador 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 retornado do CArchive::GetObjectSchema.
Chamada serializar diretamente
Em muitos casos, a sobrecarga do esquema de arquivar geral do objeto de WriteObject e ReadObject não é necessário. Esse é o caso comum de serializar os dados em um CDocument.Nesse caso, a Serialize método para o CDocument é chamado diretamente, não com os operadores extrair ou inserção. O Sumário do documento por sua vez pode usar o esquema de arquivar mais geral do objeto.
De chamadaSerialize diretamente tem as seguintes vantagens e desvantagens:
Não há extras bytes são adicionados para o arquivar antes ou depois que o objeto é serializado.Isso não apenas torna os dados salvos menores, mas permite que você implemente Serialize rotinas que podem lidar com qualquer formato de arquivo.
O MFC está sintonizado assim o WriteObject e ReadObject implementações e coleções relacionadas serão não vinculadas ao seu aplicativo a menos que seja necessário o esquema de arquivar mais geral do objeto para algum Outros fim.
Seu código não precisa recuperar números de esquema antigas.Isso torna seu código de serialização documento responsáveis por números de esquema de codificação, números da versão do formato de arquivo ou qualquer identificando os números de você usar no início de seus arquivos de dados.
Qualquer objeto é serializado com uma telefonar direta para Serialize não deve usar CArchive::GetObjectSchema ou deve alça de um valor retornado (UINT) -1 indicando que a versão era desconhecida.
Porque Serialize é chamado diretamente no seu documento, não é possível geralmente para subobjetos do documento para arquivar as referências a seu documento pai. Esses objetos devem ser dada um ponteiro para o seu documento contêiner explicitamente ou usar CArchive::MapObject função para mapear o CDocument ponteiro para um PID antes desses ponteiros de retorno são arquivados.
sistema autônomo observado anteriormente, você deve codificar a versão e informações de classe você mesmo quando você telefonar Serialize diretamente, permitindo que você alterar o formato posteriormente e ainda manter compatibilidade com arquivos mais antigos. The CArchive::SerializeClass a função pode ser chamada explicitamente antes de serializar um objeto diretamente ou antes de chamar uma classe base.