Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Esta nota descreve as rotinas MFC que dão suporte a objetos C++ persistentes e ao formato dos dados do objeto quando eles são armazenados em um arquivo. Isso se aplica apenas a classes com as macros DECLARE_SERIAL e IMPLEMENT_SERIAL .
O problema
A implementação do MFC para dados persistentes armazena dados para muitos objetos em uma única parte contígua de um arquivo. O método do Serialize objeto converte os dados do objeto em um formato binário compacto.
A implementação garante que todos os dados sejam salvos no mesmo formato usando a Classe CArchive. Ele usa um CArchive objeto como tradutor. Esse objeto persiste desde o momento em que é criado até que você chame 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 dos 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. O código do usuário não chama diretamente ReadObject e WriteObject. Em vez disso, esses objetos são usados por operadores de extração e inserção seguros de tipo específicos da classe, que são gerados automaticamente pelas macros DECLARE_SERIAL e IMPLEMENT_SERIAL. O código a seguir mostra como WriteObject e ReadObject são implicitamente 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 no repositório (CArchive::WriteObject)
O método CArchive::WriteObject grava dados de cabeçalho usados para reconstruir o objeto. Esses 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 gravado, de modo que apenas uma única cópia seja salva, independentemente do número de ponteiros para esse objeto (incluindo ponteiros circulares).
Salvar (inserir) e restaurar (extrair) objetos depende de várias "constantes manifestas". Estes são valores armazenados em binário e fornecem informações importantes para o arquivo (observe que o prefixo "w" indica quantidades de 16 bits):
| Etiqueta | Descrição |
|---|---|
| wNullTag | Usado para ponteiros de objeto NULL (0). |
| wNewClassTag | Indica que a descrição da classe a seguir é nova neste contexto de arquivo (-1). |
| wOldClassTag | Indica que a classe do objeto que está sendo lido foi vista neste contexto (0x8000). |
Ao armazenar objetos, o arquivo mantém um CMapPtrToPtr (o m_pStoreMap) que é um mapeamento de um objeto armazenado para um PID (identificador persistente) de 32 bits. Um PID é atribuído a cada objeto exclusivo e a cada nome de classe exclusivo que é salvo no contexto do arquivo. Esses PIDs são entregues sequencialmente a partir de 1. Esses PIDs não têm nenhum significado fora do escopo do arquivo morto e, em particular, não devem ser confundidos com números de registro ou outros itens de identidade.
Na classe, os CArchive PIDs são de 32 bits, mas são gravados como de 16 bits, a menos que sejam maiores que 0x7FFE. PIDs grandes são gravados como 0x7FFF seguidos pelo PID de 32 bits. Isso mantém a compatibilidade com projetos que foram criados em versões anteriores.
Quando uma requisição é feita para armazenar um objeto em um arquivo (geralmente usando o operador de inserção global), verifica-se um ponteiro CObject NULL. Se o ponteiro for NULL, o wNullTag será inserido no fluxo de arquivo.
Se o ponteiro não for NULL e puder ser serializado (a classe é uma DECLARE_SERIAL classe), o código verificará o m_pStoreMap para ver se o objeto já foi salvo. Caso exista, o código insere o PID de 32 bits associado a esse objeto no fluxo de arquivamento.
Se o objeto não tiver sido salvo antes, há duas possibilidades a serem consideradas: tanto o objeto quanto o tipo exato (ou seja, classe) do objeto são novos nesse contexto de arquivo morto ou o objeto é de um tipo exato já visto. Para determinar se o tipo foi visto, o código consulta o m_pStoreMap de um objeto CRuntimeClass que corresponde ao CRuntimeClass objeto associado ao objeto que está sendo salvo. Se houver uma correspondência, WriteObject insere uma etiqueta que é o resultado bit a bit OR de wOldClassTag e esse índice. Se o CRuntimeClass for novo nesse contexto de arquivo, WriteObject atribui um novo PID a essa classe e a insere no arquivo, precedida pelo valor wNewClassTag.
O descritor dessa classe é inserido no arquivo usando o método CRuntimeClass::Store.
CRuntimeClass::Store insere o número de 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 unicidade do arquivo entre diferentes aplicativos. Portanto, você deve marcar seus arquivos de dados para evitar corrupção. Após a inserção das informações de classe, o arquivo coloca o objeto no m_pStoreMap e então chama o Serialize método para inserir dados específicos da classe. Colocar o objeto no m_pStoreMap antes de chamar Serialize impede que várias cópias do objeto sejam salvas no repositório.
Ao retornar ao chamador inicial (geralmente a raiz da rede de objetos), você deve chamar CArchive::Close. Se você planeja executar outras operações do CFile, deve chamar o CArchive método Flush para evitar a corrupção do arquivo.
Observação
Essa implementação impõe um limite rígido de 0x3FFFFFFE índices por contexto de arquivo. Esse número representa o número máximo de objetos e classes únicos que podem ser salvos em um único arquivo, mas um único arquivo no disco pode ter um número ilimitado de contextos de arquivo.
Carregando objetos do Armazenamento (CArchive::ReadObject)
Carregar objetos (extração) usa o CArchive::ReadObject método e é o inverso de WriteObject. Assim como 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 do tipo seguro que invoca ReadObject com o esperado CRuntimeClass. Isso garante a integridade do tipo da operação de extração.
Como a WriteObject implementação atribuiu PIDs crescentes, começando com 1 (0 é predefinido como o objeto NULL), a ReadObject implementação pode usar uma matriz para manter o estado do contexto do arquivo. Quando um PID é lido do repositório, se o PID é maior que o limite superior atual do m_pLoadArray, ReadObject sabe que um novo objeto (ou descrição de classe) segue.
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 da classe. O esquema refere-se à implementação da classe, não ao número de vezes que um determinado objeto se tornou persistente (geralmente chamado de versão do objeto).
Se você pretende manter várias implementações diferentes da mesma classe ao longo do tempo, incrementar o esquema conforme você revisa a implementação do método do Serialize objeto permitirá que você escreva código que possa carregar objetos armazenados usando versões mais antigas da implementação.
O CArchive::ReadObject método gerará um CArchiveException quando encontrar um número de esquema no repositório persistente que difere do número de esquema da descrição da classe na memória. Não é fácil se recuperar dessa exceção.
Você pode usar VERSIONABLE_SCHEMA combinado com ( OR bit a bit) sua versão de esquema para impedir que essa exceção seja gerada. Ao usar VERSIONABLE_SCHEMA, seu código pode tomar a ação apropriada em sua Serialize função verificando o valor retornado de CArchive::GetObjectSchema.
Chamando Serializar diretamente
Em muitos casos, a sobrecarga do esquema geral de arquivo de objeto de WriteObject e ReadObject não é necessária. Esse é o caso comum de serializar os dados em um CDocument. Nesse caso, o método Serialize do CDocument é chamado diretamente, em vez de usar os operadores de extrair ou inserir. O conteúdo do documento, por sua vez, pode usar o esquema de arquivo de objeto mais geral.
Chamar Serialize diretamente tem as seguintes vantagens e desvantagens:
Nenhum byte extra é adicionado ao arquivo antes ou depois que o objeto é serializado. Isso não só torna os dados salvos menores, mas permite implementar
Serializerotinas que podem lidar com qualquer formato de arquivo.O MFC é ajustado para que as
WriteObjectimplementações eReadObjectcoleções relacionadas não sejam vinculadas ao seu aplicativo, a menos que você precise do esquema de arquivamento de objetos mais geral para alguma outra finalidade.Seu código não precisa se recuperar de números de esquema antigos. Isso torna o código de serialização do documento responsável pela codificação de números de esquema, números de versão de formato de arquivo ou quaisquer números de identificação usados no início dos arquivos de dados.
Qualquer objeto que seja serializado com uma chamada direta a
Serializenão deve utilizarCArchive::GetObjectSchemaou deve lidar com um valor de retorno de (UINT)-1, indicando que a versão era desconhecida.
Como Serialize é chamado diretamente em seu documento, geralmente não é possível que os sub-objetos do documento arquivem referências ao documento pai. Esses objetos devem receber explicitamente um ponteiro para o documento de contêiner, ou você deve usar a função CArchive::MapObject para mapear o ponteiro CDocument para um PID antes que esses ponteiros de retorno sejam arquivados.
Conforme observado anteriormente, você mesmo deve codificar as informações de versão e classe ao chamar Serialize diretamente, permitindo que você altere o formato mais tarde, mantendo a compatibilidade com arquivos mais antigos. A CArchive::SerializeClass função pode ser chamada explicitamente antes de serializar diretamente um objeto ou antes de chamar uma classe base.