TN002: 영구 개체 데이터 형식

이 참고에서는 영구 C++ 개체를 지원하는 MFC 루틴과 파일에 저장된 개체 데이터의 형식에 대해 설명합니다. 이는 DECLARE_SERIAL 및 IMPLEMENT_SERIAL 매크로가 있는 클래스에만 적용됩니다.

문제

영구 데이터에 대한 MFC 구현은 파일의 인접한 단일 부분에 여러 개체에 대한 데이터를 저장합니다. 개체의 Serialize 메서드는 개체의 데이터를 컴팩트한 이진 형식으로 변환합니다.

구현은 모든 데이터가 CArchive 클래스를 사용하여 동일한 형식으로 저장되도록 보장합니다. 개체를 CArchive 번역기로 사용합니다. 이 개체는 생성된 시간부터 CArchive::Close를 호출할 때까지 유지됩니다. 이 메서드는 프로그래머가 명시적으로 호출하거나 프로그램이 포함된 범위를 종료할 때 소멸자가 암시적으로 호출할 수 있습니다 CArchive.

이 참고에서는 CArchive::ReadObject 및 CArchive::WriteObject 멤버CArchive 구현에 대해 설명합니다. Arcobj.cpp에서 이러한 함수에 대한 코드와 Arccore.cpp에 대한 CArchive 기본 구현을 찾을 수 있습니다. 사용자 코드는 직접 호출 ReadObjectWriteObject 하지 않습니다. 대신 이러한 개체는 DECLARE_SERIAL 및 IMPLEMENT_SERIAL 매크로에 의해 자동으로 생성되는 클래스별 형식 안전 삽입 및 추출 연산자에서 사용됩니다. 다음 코드는 암시적으로 호출되는 방법과 WriteObjectReadObject 방법을 보여줍니다.

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))

Store에 개체 저장(CArchive::WriteObject)

메서드 CArchive::WriteObject 는 개체를 재구성하는 데 사용되는 헤더 데이터를 씁니다. 이 데이터는 개체의 형식과 개체의 상태라는 두 부분으로 구성됩니다. 또한 이 메서드는 기본 작성 중인 개체의 ID를 확인하여 해당 개체에 대한 포인터 수(순환 포인터 포함)에 관계없이 단일 복사본만 저장되도록 합니다.

개체 저장(삽입) 및 복원(추출)은 여러 "매니페스트 상수"를 사용합니다. 이진 파일에 저장되고 보관에 중요한 정보를 제공하는 값입니다("w" 접두사는 16비트 수량을 나타낸다).

태그 설명
wNullTag NULL 개체 포인터(0)에 사용됩니다.
wNewClassTag 다음에 오는 클래스 설명이 이 보관 컨텍스트(-1)에 새로 추가되었음을 나타냅니다.
wOldClassTag 읽는 개체의 클래스가 이 컨텍스트(0x8000)에 표시되었음을 나타냅니다.

개체를 저장할 때 보관 파일은 저장된 개체에서 PID(32비트 영구 식별자)로 매핑되는 CMapPtrToPtr(m_pStoreMap)를 기본. PID는 보관의 컨텍스트에 저장된 모든 고유 개체 및 모든 고유 클래스 이름에 할당됩니다. 이러한 PID는 1부터 순차적으로 전달됩니다. 이러한 PID는 보관 범위 외부에 아무런 의미가 없으며, 특히 레코드 번호 또는 기타 ID 항목과 혼동해서는 안 됩니다.

CArchive 클래스에서 PID는 32비트이지만 0x7FFE보다 크지 않으면 16비트로 작성됩니다. 대형 PID는 0x7FFF 32비트 PID로 작성됩니다. 이 기본 이전 버전에서 만든 프로젝트와의 호환성을 확인합니다.

일반적으로 전역 삽입 연산자를 사용하여 개체를 보관에 저장하도록 요청하면 NULL CObject 포인터에 대한 검사 수행됩니다. 포인터가 NULL 이면 wNullTag 가 보관 스트림에 삽입됩니다.

포인터가 NULL이 아니고 serialize할 수 있는 경우(클래스가 클래스) DECLARE_SERIAL 코드는 개체가 이미 저장되었는지 여부를 확인하기 위해 m_pStoreMap 검사. 있는 경우 코드는 해당 개체와 연결된 32비트 PID를 보관 스트림에 삽입합니다.

개체가 이전에 저장되지 않은 경우 개체와 개체의 정확한 형식(즉, 클래스)이 모두 이 보관 컨텍스트에 새로 추가되었거나 개체가 이미 표시된 정확한 형식이라는 두 가지 가능성을 고려해야 합니다. 형식이 표시되었는지 여부를 확인하기 위해 코드는 저장되는 개체와 연결된 개체와 일치하는 CRuntimeClass CRuntimeClass 개체에 대한 m_pStoreMap 쿼리합니다. 일치하는 WriteObject 항목이 있는 경우 wOldClassTag 및 이 인덱스의 비트 단위 OR 태그를 삽입합니다. 이 보관 컨텍스트 WriteObjectCRuntimeClass 새로운 경우 해당 클래스에 새 PID를 할당하고 wNewClassTag 값 앞에 있는 보관에 삽입합니다.

이 클래스의 설명자는 메서드를 사용하여 CRuntimeClass::Store 보관에 삽입됩니다. CRuntimeClass::Store 는 클래스의 스키마 번호(아래 참조) 및 클래스의 ASCII 텍스트 이름을 삽입합니다. ASCII 텍스트 이름을 사용하면 애플리케이션 간에 보관의 고유성이 보장되지 않습니다. 따라서 손상을 방지하기 위해 데이터 파일에 태그를 지정해야 합니다. 클래스 정보를 삽입한 후 보관 파일은 개체 를 m_pStoreMap 배치한 다음 메서드를 Serialize 호출하여 클래스별 데이터를 삽입합니다. 호출 Serialize 하기 전에 개체를 m_pStoreMap 배치하면 개체의 여러 복사본이 저장소에 저장되지 않습니다.

초기 호출자(일반적으로 개체 네트워크의 루트)로 돌아갈 때 CArchive::Close를 호출해야 합니다. 다른 CFile작업을 수행하려는 경우 보관 파일이 손상되지 않도록 플러시 메서드를 호출 CArchive 해야 합니다.

참고 항목

이 구현은 보관 컨텍스트당 0x3FFFFFFE 인덱스의 하드 제한을 적용합니다. 이 숫자는 단일 보관 파일에 저장할 수 있는 고유 개체 및 클래스의 최대 수를 나타내지만 단일 디스크 파일의 보관 컨텍스트 수는 무제한일 수 있습니다.

스토어에서 개체 로드(CArchive::ReadObject)

로드 (추출) 개체 메서드를 CArchive::ReadObject 사용 하 고의 WriteObject반대입니다. 마찬가지로WriteObject, ReadObject 사용자 코드에서 직접 호출되지 않습니다. 사용자 코드는 예상CRuntimeClass된 대로 호출하는 형식이 안전한 추출 연산자를 호출 ReadObject 해야 합니다. 이렇게 하면 추출 작업의 형식 무결성이 보장됩니다.

WriteObject 1(0은 NULL 개체로 미리 정의됨)ReadObject부터 증가하는 PID가 할당된 구현이므로 구현은 배열을 사용하여 보관 컨텍스트의 상태를 기본 파악할 수 있습니다. 저장소에서 PID를 읽을 때 PID가 m_pLoadArrayReadObject 현재 상한보다 큰 경우 새 개체(또는 클래스 설명)가 뒤따른다는 것을 알고 있습니다.

스키마 번호

클래스 메서드가 발견될 때 IMPLEMENT_SERIAL 클래스에 할당되는 스키마 번호는 클래스 구현의 "버전"입니다. 스키마는 지정된 개체가 영구적으로 만들어진 횟수가 아니라 클래스의 구현을 나타냅니다(일반적으로 개체 버전이라고 함).

시간이 지남에 따라 동일한 클래스의 여러 가지 구현을 기본하려는 경우 개체의 Serialize 메서드 구현을 수정할 때 스키마를 증가시켜 이전 버전의 구현을 사용하여 저장된 개체를 로드할 수 있는 코드를 작성할 수 있습니다.

이 메서드는 CArchive::ReadObject 메모리에 있는 클래스 설명의 스키마 번호와 다른 영구 저장소의 스키마 번호가 발견되면 CArchiveException 을 throw합니다. 이 예외에서 복구하는 것은 쉽지 않습니다.

스키마 버전과 결합된(비트 OR)을 사용하여 VERSIONABLE_SCHEMA 이 예외가 throw되지 않도록 할 수 있습니다. 코드를 사용하면 VERSIONABLE_SCHEMACArchive::GetObjectSchema의 반환 값을 검사 함수에서 적절한 작업을 수행할 Serialize 수 있습니다.

직렬화 직접 호출

대부분의 경우 일반 개체 보관 구성표의 WriteObject 오버헤드가 ReadObject 필요하지 않습니다. 데이터를 CDocument로 직렬화하는 일반적인 경우입니다. 이 경우 메서드는 SerializeCDocument 추출 또는 삽입 연산자가 아니라 직접 호출됩니다. 문서의 내용은 더 일반적인 개체 보관 체계를 사용할 수 있습니다.

직접 호출 Serialize 하면 다음과 같은 장점과 단점이 있습니다.

  • 개체가 serialize되기 전이나 후에 추가 바이트가 보관 파일에 추가되지 않습니다. 이렇게 하면 저장된 데이터를 더 작게 만들 뿐만 아니라 파일 형식을 처리할 수 있는 루틴을 구현 Serialize 할 수 있습니다.

  • MFC는 다른 용도로 보다 일반적인 개체 보관 구성표가 필요하지 않으면 구현 및 관련 컬렉션이 애플리케이션에 연결되지 않도록 WriteObjectReadObject 조정됩니다.

  • 코드는 이전 스키마 번호에서 복구할 필요가 없습니다. 이렇게 하면 문서 serialization 코드가 스키마 번호, 파일 형식 버전 번호 또는 데이터 파일 시작 시 사용하는 식별 번호를 인코딩하는 작업을 담당합니다.

  • 직접 호출로 직렬화된 개체는 버전을 알 수 없음을 Serialize 나타내는 반환 값(UINT)-1을 사용하지 CArchive::GetObjectSchema 않거나 처리해야 합니다.

Serialize 문서에서 직접 호출되므로 일반적으로 문서의 하위 개체가 부모 문서에 대한 참조를 보관할 수 없습니다. 이러한 개체는 컨테이너 문서에 대한 포인터를 명시적으로 지정해야 합니다. 또는 CArchive::MapObject 함수를 사용하여 포인터를 PID에 매핑 CDocument 한 후 이러한 뒤로 포인터를 보관해야 합니다.

앞에서 설명한 것처럼 직접 호출 Serialize 할 때 버전 및 클래스 정보를 직접 인코딩해야 하므로 나중에 형식을 변경할 수 있도록 기본 이전 파일과의 이전 버전과의 호환성을 유지해야 합니다. CArchive::SerializeClass 개체를 직접 직렬화하기 전에 또는 기본 클래스를 호출하기 전에 함수를 명시적으로 호출할 수 있습니다.

참고 항목

번호별 기술 참고 사항
범주별 기술 참고 사항